bitboss-ui 2.1.134 → 3.0.0-alpha.1
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/LICENSE +21 -0
- package/README.md +244 -21
- package/bin/bitboss-ui-mcp.mjs +557 -0
- package/bin/bitboss-ui.mjs +626 -0
- package/dist/ai/BbAccordion.md +522 -113
- package/dist/ai/BbAlert.md +377 -197
- package/dist/ai/BbAsterisk.md +46 -0
- package/dist/ai/BbAvatar.md +454 -178
- package/dist/ai/BbBadge.md +695 -225
- package/dist/ai/BbBadgeButton.md +54 -0
- package/dist/ai/BbBaseButton.md +489 -0
- package/dist/ai/BbBaseCheckbox.md +49 -0
- package/dist/ai/BbBaseCheckboxGroup.md +64 -0
- package/dist/ai/BbBaseCheckboxIcon.md +174 -0
- package/dist/ai/BbBaseColorInput.md +64 -0
- package/dist/ai/BbBaseColorPalette.md +39 -0
- package/dist/ai/BbBaseDatePicker.md +42 -0
- package/dist/ai/BbBaseDatePickerInput.md +73 -0
- package/dist/ai/BbBaseInputContainer.md +48 -0
- package/dist/ai/BbBaseNumberInput.md +71 -0
- package/dist/ai/BbBaseRadio.md +46 -0
- package/dist/ai/BbBaseRadioGroup.md +63 -0
- package/dist/ai/BbBaseRadioIcon.md +187 -0
- package/dist/ai/BbBaseRating.md +51 -0
- package/dist/ai/BbBaseSelect.md +88 -0
- package/dist/ai/BbBaseSlider.md +56 -0
- package/dist/ai/BbBaseSwitch.md +49 -0
- package/dist/ai/BbBaseSwitchGroup.md +64 -0
- package/dist/ai/BbBaseSwitchIcon.md +184 -0
- package/dist/ai/BbBaseTag.md +68 -0
- package/dist/ai/BbBaseTextInput.md +75 -0
- package/dist/ai/BbBaseTextarea.md +62 -0
- package/dist/ai/BbBaseTimePickerInput.md +62 -0
- package/dist/ai/BbBreadcrumbs.md +542 -503
- package/dist/ai/BbButton.md +767 -636
- package/dist/ai/BbCheckbox.md +572 -254
- package/dist/ai/BbCheckboxGroup.md +561 -361
- package/dist/ai/BbCollapsible.md +419 -103
- package/dist/ai/BbColorInput.md +529 -323
- package/dist/ai/BbColorPalette.md +565 -330
- package/dist/ai/BbConfirm.md +612 -136
- package/dist/ai/BbConfirmPortal.md +28 -0
- package/dist/ai/BbDatePicker.md +244 -0
- package/dist/ai/BbDatePickerInput.md +706 -388
- package/dist/ai/BbDialog.md +770 -99
- package/dist/ai/BbDropdown.md +1054 -763
- package/dist/ai/BbDropdownButton.md +511 -615
- package/dist/ai/BbDropdownGroup.md +34 -0
- package/dist/ai/BbDropzone.md +1148 -436
- package/dist/ai/BbForm.md +239 -0
- package/dist/ai/BbIcon.md +379 -212
- package/dist/ai/BbIndicator.md +283 -0
- package/dist/ai/BbNumberInput.md +713 -355
- package/dist/ai/BbOffCanvas.md +875 -498
- package/dist/ai/BbPagination.md +483 -535
- package/dist/ai/BbPopover.md +583 -546
- package/dist/ai/BbProgress.md +456 -69
- package/dist/ai/BbRadio.md +343 -229
- package/dist/ai/BbRadioGroup.md +426 -353
- package/dist/ai/BbRating.md +361 -226
- package/dist/ai/BbSelect.md +1380 -536
- package/dist/ai/BbSelectPopover.md +1240 -1579
- package/dist/ai/BbSlider.md +478 -257
- package/dist/ai/BbSmoothHeight.md +472 -139
- package/dist/ai/BbSpinner.md +718 -131
- package/dist/ai/BbSwitch.md +549 -119
- package/dist/ai/BbSwitchGroup.md +503 -214
- package/dist/ai/BbTable.md +1956 -2358
- package/dist/ai/BbTabs.md +1113 -0
- package/dist/ai/BbTabsList.md +29 -0
- package/dist/ai/BbTabsPanes.md +27 -0
- package/dist/ai/BbTabsRoot.md +57 -0
- package/dist/ai/BbTag.md +393 -292
- package/dist/ai/BbTextInput.md +852 -333
- package/dist/ai/BbTextarea.md +836 -255
- package/dist/ai/BbTimePicker.md +231 -0
- package/dist/ai/BbTimePickerInput.md +196 -0
- package/dist/ai/BbToast.md +624 -196
- package/dist/ai/BbToastPortal.md +28 -0
- package/dist/ai/BbTooltip.md +443 -346
- package/dist/ai/BbTree.md +984 -235
- package/dist/ai/ChipsBox.md +27 -199
- package/dist/ai/ClearableButton.md +21 -55
- package/dist/ai/CommaBox.md +25 -200
- package/dist/ai/CommonInputWrapper.md +42 -0
- package/dist/ai/CommonPopover.md +30 -510
- package/dist/ai/ErrorIcon.md +19 -56
- package/dist/ai/FlatListBox.md +35 -379
- package/dist/ai/GroupedListBox.md +37 -535
- package/dist/ai/GuidesComponent.md +21 -0
- package/dist/ai/ListBox.md +38 -231
- package/dist/ai/OptionsContainer.md +34 -253
- package/dist/ai/WarningIcon.md +21 -0
- package/dist/ai/components.json +15883 -0
- package/dist/ai/composables/useBbConfig.md +80 -0
- package/dist/ai/composables/useBbFormContext.md +110 -0
- package/dist/ai/composables/useBbTableContext.md +164 -0
- package/dist/ai/composables/useBbTabsContext.md +118 -0
- package/dist/ai/composables/useBroadcastChannelInstance.md +97 -0
- package/dist/ai/composables/useConfirm.md +306 -0
- package/dist/ai/composables/useCountdown.md +84 -0
- package/dist/ai/composables/useId.md +60 -0
- package/dist/ai/composables/useMobile.md +85 -0
- package/dist/ai/composables/useToast.md +230 -0
- package/dist/ai/guides/agent-contract.md +108 -0
- package/dist/ai/guides/ai-router.md +111 -0
- package/dist/ai/guides/app-layout.md +295 -0
- package/dist/ai/guides/coherence-playbook.md +136 -0
- package/dist/ai/guides/component-picker.md +195 -0
- package/dist/ai/guides/design-language.md +461 -0
- package/dist/ai/guides/design-tokens.md +424 -0
- package/dist/ai/guides/fetch-items-playbook.md +161 -0
- package/dist/ai/guides/floating-bottom-panel.md +191 -0
- package/dist/ai/guides/icons-policy.md +257 -0
- package/dist/ai/guides/inertia-helpers.md +1394 -0
- package/dist/ai/guides/inline-edit-playbook.md +258 -0
- package/dist/ai/guides/installation-and-plugin-setup.md +377 -0
- package/dist/ai/guides/migration/components/bb-alert.md +37 -0
- package/dist/ai/guides/migration/components/bb-avatar.md +24 -0
- package/dist/ai/guides/migration/components/bb-badge.md +110 -0
- package/dist/ai/guides/migration/components/bb-breadcrumbs.md +26 -0
- package/dist/ai/guides/migration/components/bb-button.md +55 -0
- package/dist/ai/guides/migration/components/bb-checkbox-group.md +54 -0
- package/dist/ai/guides/migration/components/bb-checkbox.md +45 -0
- package/dist/ai/guides/migration/components/bb-chip.md +35 -0
- package/dist/ai/guides/migration/components/bb-collapsible.md +26 -0
- package/dist/ai/guides/migration/components/bb-color-input.md +23 -0
- package/dist/ai/guides/migration/components/bb-color-palette.md +31 -0
- package/dist/ai/guides/migration/components/bb-date-picker-input.md +84 -0
- package/dist/ai/guides/migration/components/bb-dialog.md +46 -0
- package/dist/ai/guides/migration/components/bb-dropdown-button.md +31 -0
- package/dist/ai/guides/migration/components/bb-dropdown.md +36 -0
- package/dist/ai/guides/migration/components/bb-dropzone.md +27 -0
- package/dist/ai/guides/migration/components/bb-icon.md +27 -0
- package/dist/ai/guides/migration/components/bb-off-canvas.md +25 -0
- package/dist/ai/guides/migration/components/bb-offcanvas.md +54 -0
- package/dist/ai/guides/migration/components/bb-pagination.md +24 -0
- package/dist/ai/guides/migration/components/bb-popover.md +52 -0
- package/dist/ai/guides/migration/components/bb-rating.md +24 -0
- package/dist/ai/guides/migration/components/bb-select-popover.md +58 -0
- package/dist/ai/guides/migration/components/bb-select.md +68 -0
- package/dist/ai/guides/migration/components/bb-slider.md +25 -0
- package/dist/ai/guides/migration/components/bb-smooth-height.md +45 -0
- package/dist/ai/guides/migration/components/bb-spinner.md +23 -0
- package/dist/ai/guides/migration/components/bb-table.md +66 -0
- package/dist/ai/guides/migration/components/bb-tabs.md +47 -0
- package/dist/ai/guides/migration/components/bb-tag.md +29 -0
- package/dist/ai/guides/migration/components/bb-text-input.md +37 -0
- package/dist/ai/guides/migration/components/bb-toast.md +47 -0
- package/dist/ai/guides/migration/components/bb-tooltip.md +34 -0
- package/dist/ai/guides/migration/components/bb-tree.md +77 -0
- package/dist/ai/guides/migration/components/use-confirm.md +46 -0
- package/dist/ai/guides/migration/v2-to-v3.md +649 -0
- package/dist/ai/guides/options-items-playbook.md +222 -0
- package/dist/ai/guides/page-shell.md +314 -0
- package/dist/ai/guides/validated-forms.md +177 -0
- package/dist/ai/index.md +156 -32
- package/dist/ai/recipes/inertia/approvals-inbox.md +1598 -0
- package/dist/ai/recipes/inertia/inline-edit-workspace.md +1733 -0
- package/dist/ai/recipes/inertia/onboarding.md +1765 -0
- package/dist/ai/recipes/inertia/ownership-atlas.md +1281 -0
- package/dist/ai/recipes/inertia/record-form.md +1442 -0
- package/dist/ai/recipes/inertia/records-workspace.md +1328 -0
- package/dist/ai/recipes/inertia/upload-center.md +1926 -0
- package/dist/ai/recipes/inertia/wizard-form.md +1477 -0
- package/dist/ai/recipes/nuxt/approvals-inbox.md +1561 -0
- package/dist/ai/recipes/nuxt/inline-edit-workspace.md +1690 -0
- package/dist/ai/recipes/nuxt/onboarding.md +1698 -0
- package/dist/ai/recipes/nuxt/record-form.md +1456 -0
- package/dist/ai/recipes/nuxt/records-workspace.md +1314 -0
- package/dist/ai/recipes/nuxt/upload-center.md +1847 -0
- package/dist/ai/recipes/nuxt/wizard-form.md +1486 -0
- package/dist/ai/recipes/vue/approvals-inbox.md +1479 -0
- package/dist/ai/recipes/vue/inline-edit-workspace.md +1581 -0
- package/dist/ai/recipes/vue/onboarding.md +1653 -0
- package/dist/ai/recipes/vue/record-form.md +1540 -0
- package/dist/ai/recipes/vue/records-workspace.md +1230 -0
- package/dist/ai/recipes/vue/upload-center.md +1806 -0
- package/dist/ai/recipes/vue/wizard-form.md +1418 -0
- package/dist/ai/source/BbAccordion.md +122 -0
- package/dist/ai/source/BbAlert.md +396 -0
- package/dist/ai/source/BbAsterisk.md +32 -0
- package/dist/ai/source/BbAvatar.md +256 -0
- package/dist/ai/source/BbBadge.md +560 -0
- package/dist/ai/source/BbBadgeButton.md +490 -0
- package/dist/ai/source/BbBaseButton.md +682 -0
- package/dist/ai/source/BbBaseCheckbox.md +459 -0
- package/dist/ai/source/BbBaseCheckboxGroup.md +554 -0
- package/dist/ai/source/BbBaseCheckboxIcon.md +201 -0
- package/dist/ai/source/BbBaseColorInput.md +413 -0
- package/dist/ai/source/BbBaseColorPalette.md +934 -0
- package/dist/ai/source/BbBaseDatePicker.md +1001 -0
- package/dist/ai/source/BbBaseDatePickerInput.md +1781 -0
- package/dist/ai/source/BbBaseInputContainer.md +1148 -0
- package/dist/ai/source/BbBaseNumberInput.md +615 -0
- package/dist/ai/source/BbBaseRadio.md +377 -0
- package/dist/ai/source/BbBaseRadioGroup.md +499 -0
- package/dist/ai/source/BbBaseRadioIcon.md +153 -0
- package/dist/ai/source/BbBaseRating.md +573 -0
- package/dist/ai/source/BbBaseSelect.md +895 -0
- package/dist/ai/source/BbBaseSlider.md +1085 -0
- package/dist/ai/source/BbBaseSwitch.md +246 -0
- package/dist/ai/source/BbBaseSwitchGroup.md +302 -0
- package/dist/ai/source/BbBaseSwitchIcon.md +206 -0
- package/dist/ai/source/BbBaseTag.md +688 -0
- package/dist/ai/source/BbBaseTextInput.md +511 -0
- package/dist/ai/source/BbBaseTextarea.md +480 -0
- package/dist/ai/source/BbBaseTimePickerInput.md +1013 -0
- package/dist/ai/source/BbBreadcrumbs.md +811 -0
- package/dist/ai/source/BbButton.md +990 -0
- package/dist/ai/source/BbCheckbox.md +319 -0
- package/dist/ai/source/BbCheckboxGroup.md +412 -0
- package/dist/ai/source/BbCollapsible.md +133 -0
- package/dist/ai/source/BbColorInput.md +315 -0
- package/dist/ai/source/BbColorPalette.md +361 -0
- package/dist/ai/source/BbConfirm.md +173 -0
- package/dist/ai/source/BbConfirmPortal.md +74 -0
- package/dist/ai/source/BbDatePicker.md +725 -0
- package/dist/ai/source/BbDatePickerInput.md +518 -0
- package/dist/ai/source/BbDialog.md +812 -0
- package/dist/ai/source/BbDropdown.md +2369 -0
- package/dist/ai/source/BbDropdownButton.md +313 -0
- package/dist/ai/source/BbDropdownGroup.md +1249 -0
- package/dist/ai/source/BbDropzone.md +749 -0
- package/dist/ai/source/BbForm.md +100 -0
- package/dist/ai/source/BbIcon.md +212 -0
- package/dist/ai/source/BbIndicator.md +298 -0
- package/dist/ai/source/BbNumberInput.md +365 -0
- package/dist/ai/source/BbOffCanvas.md +1260 -0
- package/dist/ai/source/BbPagination.md +829 -0
- package/dist/ai/source/BbPopover.md +853 -0
- package/dist/ai/source/BbProgress.md +140 -0
- package/dist/ai/source/BbRadio.md +294 -0
- package/dist/ai/source/BbRadioGroup.md +401 -0
- package/dist/ai/source/BbRating.md +362 -0
- package/dist/ai/source/BbSelect.md +681 -0
- package/dist/ai/source/BbSelectPopover.md +2209 -0
- package/dist/ai/source/BbSlider.md +371 -0
- package/dist/ai/source/BbSmoothHeight.md +261 -0
- package/dist/ai/source/BbSpinner.md +127 -0
- package/dist/ai/source/BbSwitch.md +175 -0
- package/dist/ai/source/BbSwitchGroup.md +240 -0
- package/dist/ai/source/BbTable.md +3348 -0
- package/dist/ai/source/BbTabs.md +403 -0
- package/dist/ai/source/BbTabsList.md +578 -0
- package/dist/ai/source/BbTabsPanes.md +368 -0
- package/dist/ai/source/BbTabsRoot.md +372 -0
- package/dist/ai/source/BbTag.md +383 -0
- package/dist/ai/source/BbTextInput.md +345 -0
- package/dist/ai/source/BbTextarea.md +330 -0
- package/dist/ai/source/BbTimePicker.md +591 -0
- package/dist/ai/source/BbTimePickerInput.md +367 -0
- package/dist/ai/source/BbToast.md +524 -0
- package/dist/ai/source/BbToastPortal.md +482 -0
- package/dist/ai/source/BbTooltip.md +508 -0
- package/dist/ai/source/BbTree.md +319 -0
- package/dist/ai/source/ChipsBox.md +201 -0
- package/dist/ai/source/ClearableButton.md +70 -0
- package/dist/ai/source/CommaBox.md +202 -0
- package/dist/ai/source/CommonInputWrapper.md +554 -0
- package/dist/ai/source/CommonPopover.md +606 -0
- package/dist/ai/source/ErrorIcon.md +43 -0
- package/dist/ai/source/FlatListBox.md +396 -0
- package/dist/ai/source/GroupedListBox.md +540 -0
- package/dist/ai/source/GuidesComponent.md +383 -0
- package/dist/ai/source/ListBox.md +238 -0
- package/dist/ai/source/OptionsContainer.md +191 -0
- package/dist/ai/source/WarningIcon.md +47 -0
- package/dist/alert-variants.d.ts +12 -0
- package/dist/assets/css/reset.css_inline.js +4 -0
- package/dist/assets/svgs/alert.svg_raw.js +4 -0
- package/dist/assets/svgs/calendar.svg_raw.js +4 -0
- package/dist/assets/svgs/check_stroke.svg_raw.js +4 -0
- package/dist/assets/svgs/chevron_down.svg_raw.js +4 -0
- package/dist/assets/svgs/chevron_right.svg_raw.js +4 -0
- package/dist/assets/svgs/clear.svg_raw.js +4 -0
- package/dist/assets/svgs/clock.svg_raw.js +4 -0
- package/dist/assets/svgs/error.svg_raw.js +4 -0
- package/dist/assets/svgs/search.svg_raw.js +4 -0
- package/dist/assets/svgs/sort.svg_raw.js +4 -0
- package/dist/assets/svgs/spinner.svg_raw.js +4 -0
- package/dist/assets/svgs/user.svg_raw.js +4 -0
- package/dist/badge-variants.d.ts +18 -0
- package/dist/button-variants.d.ts +15 -0
- package/dist/components/BbAccordion/BbAccordion.vue.d.ts +9 -3
- package/dist/components/BbAccordion/BbAccordion.vue.js +6 -0
- package/dist/components/BbAccordion/BbAccordion.vue_vue_type_script_setup_true_lang.js +66 -0
- package/dist/components/BbAccordion/types.d.ts +10 -7
- package/dist/components/BbAlert/BbAlert.vue.d.ts +12 -3
- package/dist/components/BbAlert/BbAlert.vue.js +6 -0
- package/dist/components/BbAlert/BbAlert.vue_vue_type_script_setup_true_lang.js +65 -0
- package/dist/components/BbAlert/types.d.ts +51 -12
- package/dist/components/BbAsterisk/BbAsterisk.vue.d.ts +3 -0
- package/dist/components/BbAsterisk/BbAsterisk.vue.js +6 -0
- package/dist/components/BbAsterisk/BbAsterisk.vue_vue_type_script_setup_true_lang.js +13 -0
- package/dist/components/BbAvatar/BbAvatar.vue.d.ts +5 -0
- package/dist/components/BbAvatar/BbAvatar.vue.js +6 -0
- package/dist/components/BbAvatar/BbAvatar.vue_vue_type_script_setup_true_lang.js +78 -0
- package/dist/components/BbAvatar/types.d.ts +4 -5
- package/dist/components/BbBadge/BadgeAppendIcon.vue.d.ts +3 -0
- package/dist/components/BbBadge/BadgeAppendIcon.vue.js +5 -0
- package/dist/components/BbBadge/BadgeAppendIcon.vue_vue_type_script_setup_true_lang.js +22 -0
- package/dist/components/BbBadge/BadgeBodyContent.vue.d.ts +15 -0
- package/dist/components/BbBadge/BadgeBodyContent.vue.js +5 -0
- package/dist/components/BbBadge/BadgeBodyContent.vue_vue_type_script_setup_true_lang.js +34 -0
- package/dist/components/BbBadge/BbBadge.vue.d.ts +8 -1
- package/dist/components/BbBadge/BbBadge.vue.js +6 -0
- package/dist/components/BbBadge/BbBadge.vue_vue_type_script_setup_true_lang.js +83 -0
- package/dist/components/BbBadge/BbBadgeButton.vue.d.ts +12 -0
- package/dist/components/BbBadge/BbBadgeButton.vue.js +5 -0
- package/dist/components/BbBadge/BbBadgeButton.vue_vue_type_script_setup_true_lang.js +54 -0
- package/dist/components/BbBadge/badgeButtonContext.d.ts +27 -0
- package/dist/components/BbBadge/badgeButtonContext.js +5 -0
- package/dist/components/BbBadge/types.d.ts +61 -16
- package/dist/components/BbBaseButton/BbBaseButton.vue.d.ts +13 -0
- package/dist/components/BbBaseButton/BbBaseButton.vue.js +6 -0
- package/dist/components/BbBaseButton/BbBaseButton.vue_vue_type_script_setup_true_lang.js +171 -0
- package/dist/components/BbBaseButton/RouterComponent.vue.js +5 -0
- package/dist/components/BbBaseButton/RouterComponent.vue_vue_type_script_setup_true_lang.js +28 -0
- package/dist/components/BbBaseButton/types.d.ts +191 -0
- package/dist/components/BbBaseButton/types.js +33 -0
- package/dist/components/BbBaseCheckbox/BbBaseCheckbox.vue.d.ts +40 -0
- package/dist/components/BbBaseCheckbox/BbBaseCheckbox.vue.js +6 -0
- package/dist/components/BbBaseCheckbox/BbBaseCheckbox.vue_vue_type_script_setup_true_lang.js +145 -0
- package/dist/components/BbBaseCheckbox/types.d.ts +222 -0
- package/dist/components/BbBaseCheckboxGroup/BbBaseCheckboxGroup.vue.d.ts +19 -0
- package/dist/components/BbBaseCheckboxGroup/BbBaseCheckboxGroup.vue.js +6 -0
- package/dist/components/BbBaseCheckboxGroup/BbBaseCheckboxGroup.vue_vue_type_script_setup_true_lang.js +190 -0
- package/dist/components/BbBaseCheckboxGroup/types.d.ts +257 -0
- package/dist/components/BbBaseCheckboxIcon/BbBaseCheckboxIcon.vue.d.ts +4 -0
- package/dist/components/BbBaseCheckboxIcon/BbBaseCheckboxIcon.vue.js +6 -0
- package/dist/components/BbBaseCheckboxIcon/BbBaseCheckboxIcon.vue_vue_type_script_setup_true_lang.js +43 -0
- package/dist/components/BbBaseCheckboxIcon/types.d.ts +47 -0
- package/dist/components/BbBaseColorInput/BbBaseColorInput.vue.d.ts +42 -0
- package/dist/components/BbBaseColorInput/BbBaseColorInput.vue.js +6 -0
- package/dist/components/BbBaseColorInput/BbBaseColorInput.vue_vue_type_script_setup_true_lang.js +178 -0
- package/dist/components/BbBaseColorInput/types.d.ts +207 -0
- package/dist/components/BbBaseColorPalette/BbBaseColorPalette.vue.d.ts +13 -0
- package/dist/components/BbBaseColorPalette/BbBaseColorPalette.vue.js +6 -0
- package/dist/components/BbBaseColorPalette/BbBaseColorPalette.vue_vue_type_script_setup_true_lang.js +395 -0
- package/dist/components/BbBaseColorPalette/types.d.ts +40 -0
- package/dist/components/BbBaseDatePicker/BbBaseDatePicker.vue.js +6 -0
- package/dist/components/BbBaseDatePicker/BbBaseDatePicker.vue_vue_type_script_setup_true_lang.js +300 -0
- package/dist/components/BbBaseDatePicker/BbBaseDatePickerInputDaySelector.vue.d.ts +44 -0
- package/dist/components/BbBaseDatePicker/BbBaseDatePickerInputDaySelector.vue.js +5 -0
- package/dist/components/BbBaseDatePicker/BbBaseDatePickerInputDaySelector.vue_vue_type_script_setup_true_lang.js +310 -0
- package/dist/components/BbBaseDatePicker/BbBaseDatePickerMonthSelector.vue.d.ts +5 -0
- package/dist/components/BbBaseDatePicker/BbBaseDatePickerMonthSelector.vue.js +5 -0
- package/dist/components/BbBaseDatePicker/BbBaseDatePickerMonthSelector.vue_vue_type_script_setup_true_lang.js +63 -0
- package/dist/components/BbBaseDatePicker/BbBaseDatePickerYearSelector.vue.d.ts +5 -0
- package/dist/components/BbBaseDatePicker/BbBaseDatePickerYearSelector.vue.js +5 -0
- package/dist/components/BbBaseDatePicker/BbBaseDatePickerYearSelector.vue_vue_type_script_setup_true_lang.js +75 -0
- package/dist/components/BbBaseDatePicker/typeahead.d.ts +112 -0
- package/dist/components/BbBaseDatePicker/typeahead.js +37 -0
- package/dist/components/BbBaseDatePicker/types.d.ts +97 -0
- package/dist/components/BbBaseDatePicker/useDatePickerContext.d.ts +58 -0
- package/dist/components/BbBaseDatePicker/useDatePickerContext.js +11 -0
- package/dist/components/BbBaseDatePickerInput/BbBaseDatePickerInput.vue.d.ts +30 -0
- package/dist/components/BbBaseDatePickerInput/BbBaseDatePickerInput.vue.js +6 -0
- package/dist/components/BbBaseDatePickerInput/BbBaseDatePickerInput.vue_vue_type_script_setup_true_lang.js +713 -0
- package/dist/components/BbBaseDatePickerInput/BbBaseDatePickerTimeSelector.vue.d.ts +30 -0
- package/dist/components/BbBaseDatePickerInput/BbBaseDatePickerTimeSelector.vue.js +6 -0
- package/dist/components/BbBaseDatePickerInput/BbBaseDatePickerTimeSelector.vue_vue_type_script_setup_true_lang.js +204 -0
- package/dist/components/BbBaseDatePickerInput/types.d.ts +309 -0
- package/dist/components/BbBaseInputContainer/BbBaseInputContainer.vue.d.ts +17 -0
- package/dist/components/BbBaseInputContainer/BbBaseInputContainer.vue.js +6 -0
- package/dist/components/BbBaseInputContainer/BbBaseInputContainer.vue_vue_type_script_setup_true_lang.js +184 -0
- package/dist/components/BbBaseInputContainer/types.d.ts +185 -0
- package/dist/components/BbBaseNumberInput/BbBaseNumberInput.vue.d.ts +52 -0
- package/dist/components/BbBaseNumberInput/BbBaseNumberInput.vue.js +6 -0
- package/dist/components/BbBaseNumberInput/BbBaseNumberInput.vue_vue_type_script_setup_true_lang.js +211 -0
- package/dist/components/BbBaseNumberInput/types.d.ts +211 -0
- package/dist/components/BbBaseRadio/BbBaseRadio.vue.d.ts +33 -0
- package/dist/components/BbBaseRadio/BbBaseRadio.vue.js +6 -0
- package/dist/components/BbBaseRadio/BbBaseRadio.vue_vue_type_script_setup_true_lang.js +114 -0
- package/dist/components/BbBaseRadio/types.d.ts +179 -0
- package/dist/components/BbBaseRadioGroup/BbBaseRadioGroup.vue.d.ts +19 -0
- package/dist/components/BbBaseRadioGroup/BbBaseRadioGroup.vue.js +6 -0
- package/dist/components/BbBaseRadioGroup/BbBaseRadioGroup.vue_vue_type_script_setup_true_lang.js +172 -0
- package/dist/components/BbBaseRadioGroup/types.d.ts +215 -0
- package/dist/components/BbBaseRadioIcon/BbBaseRadioIcon.vue.d.ts +4 -0
- package/dist/components/BbBaseRadioIcon/BbBaseRadioIcon.vue.js +6 -0
- package/dist/components/BbBaseRadioIcon/BbBaseRadioIcon.vue_vue_type_script_setup_true_lang.js +28 -0
- package/dist/components/BbBaseRadioIcon/types.d.ts +41 -0
- package/dist/components/BbBaseRating/BbBaseRating.vue.d.ts +38 -0
- package/dist/components/BbBaseRating/BbBaseRating.vue.js +6 -0
- package/dist/components/BbBaseRating/BbBaseRating.vue_vue_type_script_setup_true_lang.js +184 -0
- package/dist/components/BbBaseRating/types.d.ts +168 -0
- package/dist/components/BbBaseSelect/BbBaseSelect.vue.d.ts +19 -0
- package/dist/components/BbBaseSelect/BbBaseSelect.vue.js +6 -0
- package/dist/components/BbBaseSelect/BbBaseSelect.vue_vue_type_script_setup_true_lang.js +307 -0
- package/dist/components/BbBaseSelect/types.d.ts +294 -0
- package/dist/components/BbBaseSlider/BbBaseSlider.vue.d.ts +35 -0
- package/dist/components/BbBaseSlider/BbBaseSlider.vue.js +6 -0
- package/dist/components/BbBaseSlider/BbBaseSlider.vue_vue_type_script_setup_true_lang.js +324 -0
- package/dist/components/BbBaseSlider/types.d.ts +200 -0
- package/dist/components/BbBaseSwitch/BbBaseSwitch.vue.d.ts +35 -0
- package/dist/components/BbBaseSwitch/BbBaseSwitch.vue.js +6 -0
- package/dist/components/BbBaseSwitch/BbBaseSwitch.vue_vue_type_script_setup_true_lang.js +146 -0
- package/dist/components/BbBaseSwitch/types.d.ts +10 -0
- package/dist/components/BbBaseSwitchGroup/BbBaseSwitchGroup.vue.d.ts +19 -0
- package/dist/components/BbBaseSwitchGroup/BbBaseSwitchGroup.vue.js +6 -0
- package/dist/components/BbBaseSwitchGroup/BbBaseSwitchGroup.vue_vue_type_script_setup_true_lang.js +190 -0
- package/dist/components/BbBaseSwitchGroup/types.d.ts +8 -0
- package/dist/components/BbBaseSwitchIcon/BbBaseSwitchIcon.vue.d.ts +4 -0
- package/dist/components/BbBaseSwitchIcon/BbBaseSwitchIcon.vue.js +6 -0
- package/dist/components/BbBaseSwitchIcon/BbBaseSwitchIcon.vue_vue_type_script_setup_true_lang.js +30 -0
- package/dist/components/BbBaseSwitchIcon/types.d.ts +48 -0
- package/dist/components/BbBaseTag/BbBaseTag.vue.d.ts +50 -0
- package/dist/components/BbBaseTag/BbBaseTag.vue.js +6 -0
- package/dist/components/BbBaseTag/BbBaseTag.vue_vue_type_script_setup_true_lang.js +266 -0
- package/dist/components/BbBaseTag/types.d.ts +160 -0
- package/dist/components/BbBaseTextInput/BbBaseTextInput.vue.d.ts +45 -0
- package/dist/components/BbBaseTextInput/BbBaseTextInput.vue.js +6 -0
- package/dist/components/BbBaseTextInput/BbBaseTextInput.vue_vue_type_script_setup_true_lang.js +200 -0
- package/dist/components/BbBaseTextInput/types.d.ts +209 -0
- package/dist/components/BbBaseTextarea/BbBaseTextarea.vue.d.ts +39 -0
- package/dist/components/BbBaseTextarea/BbBaseTextarea.vue.js +6 -0
- package/dist/components/BbBaseTextarea/BbBaseTextarea.vue_vue_type_script_setup_true_lang.js +175 -0
- package/dist/components/BbBaseTextarea/types.d.ts +170 -0
- package/dist/components/BbBaseTimePickerInput/BbBaseTimePickerInput.vue.d.ts +27 -0
- package/dist/components/BbBaseTimePickerInput/BbBaseTimePickerInput.vue.js +6 -0
- package/dist/components/BbBaseTimePickerInput/BbBaseTimePickerInput.vue_vue_type_script_setup_true_lang.js +517 -0
- package/dist/components/BbBaseTimePickerInput/types.d.ts +151 -0
- package/dist/components/BbBreadcrumbs/BbBreadcrumbs.vue.d.ts +0 -1
- package/dist/components/BbBreadcrumbs/BbBreadcrumbs.vue.js +6 -0
- package/dist/components/BbBreadcrumbs/BbBreadcrumbs.vue_vue_type_script_setup_true_lang.js +224 -0
- package/dist/components/BbBreadcrumbs/estimateBreadcrumbItemWidth.js +47 -0
- package/dist/components/BbBreadcrumbs/types.d.ts +33 -22
- package/dist/components/BbButton/BbButton.vue.d.ts +6 -5
- package/dist/components/BbButton/BbButton.vue.js +6 -0
- package/dist/components/BbButton/BbButton.vue_vue_type_script_setup_true_lang.js +177 -0
- package/dist/components/BbButton/types.d.ts +48 -47
- package/dist/components/BbCheckbox/BbCheckbox.vue.d.ts +4 -3
- package/dist/components/BbCheckbox/BbCheckbox.vue.js +6 -0
- package/dist/components/BbCheckbox/BbCheckbox.vue_vue_type_script_setup_true_lang.js +146 -0
- package/dist/components/BbCheckbox/types.d.ts +24 -10
- package/dist/components/BbCheckboxGroup/BbCheckboxGroup.vue.d.ts +2 -13
- package/dist/components/BbCheckboxGroup/BbCheckboxGroup.vue.js +6 -0
- package/dist/components/BbCheckboxGroup/BbCheckboxGroup.vue_vue_type_script_setup_true_lang.js +190 -0
- package/dist/components/BbCheckboxGroup/types.d.ts +44 -23
- package/dist/components/BbCollapsible/BbCollapsible.vue.d.ts +2 -1
- package/dist/components/BbCollapsible/BbCollapsible.vue.js +6 -0
- package/dist/components/BbCollapsible/BbCollapsible.vue_vue_type_script_setup_true_lang.js +31 -0
- package/dist/components/BbCollapsible/types.d.ts +10 -9
- package/dist/components/BbColorInput/BbColorInput.vue.d.ts +10 -1
- package/dist/components/BbColorInput/BbColorInput.vue.js +6 -0
- package/dist/components/BbColorInput/BbColorInput.vue_vue_type_script_setup_true_lang.js +177 -0
- package/dist/components/BbColorInput/types.d.ts +37 -11
- package/dist/components/BbColorPalette/BbColorPalette.vue.d.ts +5 -8
- package/dist/components/BbColorPalette/BbColorPalette.vue.js +5 -0
- package/dist/components/BbColorPalette/BbColorPalette.vue_vue_type_script_setup_true_lang.js +125 -0
- package/dist/components/BbColorPalette/types.d.ts +66 -53
- package/dist/components/BbConfirm/BbConfirm.vue.js +6 -0
- package/dist/components/BbConfirm/BbConfirm.vue_vue_type_script_setup_true_lang.js +130 -0
- package/dist/components/BbConfirm/BbConfirmPortal.vue.d.ts +5 -6
- package/dist/components/BbConfirm/BbConfirmPortal.vue.js +5 -0
- package/dist/components/BbConfirm/BbConfirmPortal.vue_vue_type_script_setup_true_lang.js +19 -0
- package/dist/components/BbDatePicker/BbDatePicker.vue.d.ts +37 -0
- package/dist/components/BbDatePicker/BbDatePicker.vue.js +6 -0
- package/dist/components/BbDatePicker/BbDatePicker.vue_vue_type_script_setup_true_lang.js +319 -0
- package/dist/components/BbDatePicker/types.d.ts +155 -0
- package/dist/components/BbDatePickerInput/BbDatePickerInput.vue.d.ts +3 -1
- package/dist/components/BbDatePickerInput/BbDatePickerInput.vue.js +6 -0
- package/dist/components/BbDatePickerInput/BbDatePickerInput.vue_vue_type_script_setup_true_lang.js +179 -0
- package/dist/components/BbDatePickerInput/types.d.ts +127 -16
- package/dist/components/BbDialog/BbDialog.vue.d.ts +8 -6
- package/dist/components/BbDialog/BbDialog.vue.js +6 -0
- package/dist/components/BbDialog/BbDialog.vue_vue_type_script_setup_true_lang.js +214 -0
- package/dist/components/BbDialog/types.d.ts +146 -1
- package/dist/components/BbDialog/useDialogStack.d.ts +5 -0
- package/dist/components/BbDialog/useDialogStack.js +20 -0
- package/dist/components/BbDropdown/AdaptiveDropdown.vue.d.ts +18 -0
- package/dist/components/BbDropdown/AdaptiveDropdown.vue.js +6 -0
- package/dist/components/BbDropdown/AdaptiveDropdown.vue_vue_type_script_setup_true_lang.js +116 -0
- package/dist/components/BbDropdown/BbDropdown.vue.d.ts +14 -7
- package/dist/components/BbDropdown/BbDropdown.vue.js +6 -0
- package/dist/components/BbDropdown/BbDropdown.vue_vue_type_script_setup_true_lang.js +560 -0
- package/dist/components/BbDropdown/BbDropdownGroup.vue.d.ts +44 -0
- package/dist/components/BbDropdown/BbDropdownGroup.vue.js +5 -0
- package/dist/components/BbDropdown/BbDropdownGroup.vue_vue_type_script_setup_true_lang.js +36 -0
- package/dist/components/BbDropdown/BbDropdownList.vue.d.ts +27 -0
- package/dist/components/BbDropdown/BbDropdownList.vue.js +6 -0
- package/dist/components/BbDropdown/BbDropdownList.vue_vue_type_script_setup_true_lang.js +464 -0
- package/dist/components/BbDropdown/DropdownPipelineResolver.vue.d.ts +16 -0
- package/dist/components/BbDropdown/DropdownPipelineResolver.vue.js +5 -0
- package/dist/components/BbDropdown/DropdownPipelineResolver.vue_vue_type_script_setup_true_lang.js +168 -0
- package/dist/components/BbDropdown/normalizeGroups.d.ts +18 -0
- package/dist/components/BbDropdown/normalizeGroups.js +46 -0
- package/dist/components/BbDropdown/types.d.ts +457 -45
- package/dist/components/BbDropdown/types.js +4 -0
- package/dist/components/BbDropdown/useDropdownContext.d.ts +89 -0
- package/dist/components/BbDropdown/useDropdownContext.js +9 -0
- package/dist/components/BbDropdown/useDropdownGroupRegistry.d.ts +108 -0
- package/dist/components/BbDropdown/useDropdownGroupRegistry.js +5 -0
- package/dist/components/BbDropdownButton/BbDropdownButton.vue.d.ts +3 -3
- package/dist/components/BbDropdownButton/BbDropdownButton.vue.js +6 -0
- package/dist/components/BbDropdownButton/BbDropdownButton.vue_vue_type_script_setup_true_lang.js +119 -0
- package/dist/components/BbDropdownButton/types.d.ts +45 -52
- package/dist/components/BbDropzone/BbDropzone.vue.d.ts +5 -4
- package/dist/components/BbDropzone/BbDropzone.vue.js +6 -0
- package/dist/components/BbDropzone/BbDropzone.vue_vue_type_script_setup_true_lang.js +284 -0
- package/dist/components/BbDropzone/types.d.ts +55 -7
- package/dist/components/BbIcon/BbIcon.vue.d.ts +3 -2
- package/dist/components/BbIcon/BbIcon.vue.js +6 -0
- package/dist/components/BbIcon/BbIcon.vue_vue_type_script_setup_true_lang.js +65 -0
- package/dist/components/BbIcon/types.d.ts +6 -19
- package/dist/components/BbIndicator/BbIndicator.vue.d.ts +14 -0
- package/dist/components/BbIndicator/BbIndicator.vue.js +6 -0
- package/dist/components/BbIndicator/BbIndicator.vue_vue_type_script_setup_true_lang.js +56 -0
- package/dist/components/BbIndicator/types.d.ts +49 -0
- package/dist/components/BbNumberInput/BbNumberInput.vue.d.ts +12 -6
- package/dist/components/BbNumberInput/BbNumberInput.vue.js +6 -0
- package/dist/components/BbNumberInput/BbNumberInput.vue_vue_type_script_setup_true_lang.js +194 -0
- package/dist/components/BbNumberInput/types.d.ts +31 -4
- package/dist/components/BbOffCanvas/BbOffCanvas.vue.d.ts +10 -10
- package/dist/components/BbOffCanvas/BbOffCanvas.vue.js +6 -0
- package/dist/components/BbOffCanvas/BbOffCanvas.vue_vue_type_script_setup_true_lang.js +284 -0
- package/dist/components/BbOffCanvas/types.d.ts +71 -39
- package/dist/components/BbOffCanvas/useOffCanvasStack.d.ts +25 -0
- package/dist/components/BbOffCanvas/useOffCanvasStack.js +68 -0
- package/dist/components/BbPagination/BbPagination.vue.d.ts +3 -4
- package/dist/components/BbPagination/BbPagination.vue.js +6 -0
- package/dist/components/BbPagination/BbPagination.vue_vue_type_script_setup_true_lang.js +244 -0
- package/dist/components/BbPagination/paginationFit.d.ts +38 -0
- package/dist/components/BbPagination/paginationFit.js +13 -0
- package/dist/components/BbPagination/types.d.ts +51 -16
- package/dist/components/BbPopover/BbPopover.vue.d.ts +10 -15
- package/dist/components/BbPopover/BbPopover.vue.js +6 -0
- package/dist/components/BbPopover/BbPopover.vue_vue_type_script_setup_true_lang.js +310 -0
- package/dist/components/BbPopover/escapeStack.d.ts +6 -0
- package/dist/components/BbPopover/escapeStack.js +10 -0
- package/dist/components/BbPopover/types.d.ts +84 -31
- package/dist/components/BbProgress/BbProgress.vue.d.ts +1 -0
- package/dist/components/BbProgress/BbProgress.vue.js +6 -0
- package/dist/components/BbProgress/BbProgress.vue_vue_type_script_setup_true_lang.js +36 -0
- package/dist/components/BbProgress/types.d.ts +15 -1
- package/dist/components/BbRadio/BbRadio.vue.d.ts +5 -3
- package/dist/components/BbRadio/BbRadio.vue.js +6 -0
- package/dist/components/BbRadio/BbRadio.vue_vue_type_script_setup_true_lang.js +133 -0
- package/dist/components/BbRadio/types.d.ts +23 -9
- package/dist/components/BbRadioGroup/BbRadioGroup.vue.d.ts +4 -14
- package/dist/components/BbRadioGroup/BbRadioGroup.vue.js +6 -0
- package/dist/components/BbRadioGroup/BbRadioGroup.vue_vue_type_script_setup_true_lang.js +184 -0
- package/dist/components/BbRadioGroup/types.d.ts +41 -22
- package/dist/components/BbRating/BbRating.vue.d.ts +4 -1
- package/dist/components/BbRating/BbRating.vue.js +6 -0
- package/dist/components/BbRating/BbRating.vue_vue_type_script_setup_true_lang.js +150 -0
- package/dist/components/BbRating/types.d.ts +37 -10
- package/dist/components/BbSelect/BbSelect.vue.d.ts +2 -11
- package/dist/components/BbSelect/BbSelect.vue.js +6 -0
- package/dist/components/BbSelect/BbSelect.vue_vue_type_script_setup_true_lang.js +241 -0
- package/dist/components/BbSelect/types.d.ts +105 -59
- package/dist/components/BbSelectPopover/BbSelectPopover.vue.d.ts +1 -10
- package/dist/components/BbSelectPopover/BbSelectPopover.vue.js +6 -0
- package/dist/components/BbSelectPopover/BbSelectPopover.vue_vue_type_script_setup_true_lang.js +637 -0
- package/dist/components/BbSelectPopover/types.d.ts +189 -102
- package/dist/components/BbSlider/BbSlider.vue.d.ts +3 -6
- package/dist/components/BbSlider/BbSlider.vue.js +6 -0
- package/dist/components/BbSlider/BbSlider.vue_vue_type_script_setup_true_lang.js +166 -0
- package/dist/components/BbSlider/types.d.ts +37 -13
- package/dist/components/BbSmoothHeight/BbSmoothHeight.vue.d.ts +3 -1
- package/dist/components/BbSmoothHeight/BbSmoothHeight.vue.js +6 -0
- package/dist/components/BbSmoothHeight/BbSmoothHeight.vue_vue_type_script_setup_true_lang.js +79 -0
- package/dist/components/BbSmoothHeight/types.d.ts +13 -2
- package/dist/components/BbSpinner/BbSpinner.vue.d.ts +1 -1
- package/dist/components/BbSpinner/BbSpinner.vue.js +6 -0
- package/dist/components/BbSpinner/BbSpinner.vue_vue_type_script_setup_true_lang.js +30 -0
- package/dist/components/BbSpinner/types.d.ts +10 -3
- package/dist/components/BbSwitch/BbSwitch.vue.d.ts +5 -3
- package/dist/components/BbSwitch/BbSwitch.vue.js +6 -0
- package/dist/components/BbSwitch/BbSwitch.vue_vue_type_script_setup_true_lang.js +142 -0
- package/dist/components/BbSwitch/types.d.ts +3 -1
- package/dist/components/BbSwitchGroup/BbSwitchGroup.vue.d.ts +4 -14
- package/dist/components/BbSwitchGroup/BbSwitchGroup.vue.js +6 -0
- package/dist/components/BbSwitchGroup/BbSwitchGroup.vue_vue_type_script_setup_true_lang.js +190 -0
- package/dist/components/BbSwitchGroup/types.d.ts +3 -22
- package/dist/components/BbTable/BbTable.vue.d.ts +53 -31
- package/dist/components/BbTable/BbTable.vue.js +6 -0
- package/dist/components/BbTable/BbTable.vue_vue_type_script_setup_true_lang.js +889 -0
- package/dist/components/BbTable/types.d.ts +176 -37
- package/dist/components/BbTable/utils.d.ts +9 -1
- package/dist/components/BbTable/utils.js +53 -0
- package/dist/components/BbTabs/BbTabs.vue.d.ts +20 -0
- package/dist/components/BbTabs/BbTabs.vue.js +6 -0
- package/dist/components/BbTabs/BbTabs.vue_vue_type_script_setup_true_lang.js +64 -0
- package/dist/components/BbTabs/BbTabsList.vue.d.ts +19 -0
- package/dist/components/BbTabs/BbTabsList.vue.js +6 -0
- package/dist/components/BbTabs/BbTabsList.vue_vue_type_script_setup_true_lang.js +123 -0
- package/dist/components/BbTabs/BbTabsPanes.vue.d.ts +16 -0
- package/dist/components/BbTabs/BbTabsPanes.vue.js +6 -0
- package/dist/components/BbTabs/BbTabsPanes.vue_vue_type_script_setup_true_lang.js +35 -0
- package/dist/components/BbTabs/BbTabsRoot.vue.d.ts +20 -0
- package/dist/components/BbTabs/BbTabsRoot.vue.js +5 -0
- package/dist/components/BbTabs/BbTabsRoot.vue_vue_type_script_setup_true_lang.js +55 -0
- package/dist/components/BbTabs/types.d.ts +264 -0
- package/dist/components/BbTag/BbTag.vue.d.ts +19 -6
- package/dist/components/BbTag/BbTag.vue.js +6 -0
- package/dist/components/BbTag/BbTag.vue_vue_type_script_setup_true_lang.js +187 -0
- package/dist/components/BbTag/types.d.ts +48 -17
- package/dist/components/BbTextInput/BbTextInput.vue.d.ts +10 -1
- package/dist/components/BbTextInput/BbTextInput.vue.js +6 -0
- package/dist/components/BbTextInput/BbTextInput.vue_vue_type_script_setup_true_lang.js +195 -0
- package/dist/components/BbTextInput/types.d.ts +24 -4
- package/dist/components/BbTextarea/BbTextarea.vue.d.ts +10 -1
- package/dist/components/BbTextarea/BbTextarea.vue.js +6 -0
- package/dist/components/BbTextarea/BbTextarea.vue_vue_type_script_setup_true_lang.js +178 -0
- package/dist/components/BbTextarea/types.d.ts +26 -2
- package/dist/components/BbTimePicker/BbTimePicker.vue.d.ts +33 -0
- package/dist/components/BbTimePicker/BbTimePicker.vue.js +6 -0
- package/dist/components/BbTimePicker/BbTimePicker.vue_vue_type_script_setup_true_lang.js +244 -0
- package/dist/components/BbTimePicker/types.d.ts +129 -0
- package/dist/components/BbTimePickerInput/BbTimePickerInput.vue.d.ts +25 -0
- package/dist/components/BbTimePickerInput/BbTimePickerInput.vue.js +6 -0
- package/dist/components/BbTimePickerInput/BbTimePickerInput.vue_vue_type_script_setup_true_lang.js +157 -0
- package/dist/components/BbTimePickerInput/types.d.ts +165 -0
- package/dist/components/BbToast/BbToast.vue.d.ts +2 -3
- package/dist/components/BbToast/BbToast.vue.js +6 -0
- package/dist/components/BbToast/BbToast.vue_vue_type_script_setup_true_lang.js +35 -0
- package/dist/components/BbToast/BbToastMessage.vue.d.ts +17 -8
- package/dist/components/BbToast/BbToastMessage.vue.js +5 -0
- package/dist/components/BbToast/BbToastMessage.vue_vue_type_script_setup_true_lang.js +170 -0
- package/dist/components/BbToast/BbToastPortal.vue.d.ts +30 -0
- package/dist/components/BbToast/BbToastPortal.vue.js +5 -0
- package/dist/components/BbToast/BbToastPortal.vue_vue_type_script_setup_true_lang.js +19 -0
- package/dist/components/BbToast/BbToastRegion.vue.d.ts +8 -0
- package/dist/components/BbToast/BbToastRegion.vue.js +5 -0
- package/dist/components/BbToast/BbToastRegion.vue_vue_type_script_setup_true_lang.js +100 -0
- package/dist/components/BbToast/types.d.ts +69 -5
- package/dist/components/BbTooltip/BbTooltip.vue.d.ts +4 -3
- package/dist/components/BbTooltip/BbTooltip.vue.js +6 -0
- package/dist/components/BbTooltip/BbTooltip.vue_vue_type_script_setup_true_lang.js +160 -0
- package/dist/components/BbTooltip/types.d.ts +61 -11
- package/dist/components/BbTree/BbTree.vue.d.ts +26 -13
- package/dist/components/BbTree/BbTree.vue.js +6 -0
- package/dist/components/BbTree/BbTree.vue_vue_type_script_setup_true_lang.js +55 -0
- package/dist/components/BbTree/BbTreeLevel.vue.d.ts +19 -0
- package/dist/components/BbTree/BbTreeLevel.vue.js +5 -0
- package/dist/components/BbTree/BbTreeLevel.vue_vue_type_script_setup_true_lang.js +94 -0
- package/dist/components/BbTree/context.d.ts +17 -0
- package/dist/components/BbTree/context.js +4 -0
- package/dist/components/BbTree/types.d.ts +93 -42
- package/dist/components/ChipsBox/ChipsBox.vue.d.ts +2 -1
- package/dist/components/ChipsBox/ChipsBox.vue.js +5 -0
- package/dist/components/ChipsBox/ChipsBox.vue_vue_type_script_setup_true_lang.js +98 -0
- package/dist/components/ChipsBox/types.d.ts +12 -5
- package/dist/components/ClearableButton/ClearableButton.vue.d.ts +2 -1
- package/dist/components/ClearableButton/ClearableButton.vue.js +6 -0
- package/dist/components/ClearableButton/ClearableButton.vue_vue_type_script_setup_true_lang.js +19 -0
- package/dist/components/ClearableButton/types.d.ts +7 -1
- package/dist/components/CloseIcon/CloseButton.vue.d.ts +5 -0
- package/dist/components/CloseIcon/CloseButton.vue.js +7 -0
- package/dist/components/CloseIcon/CloseButton.vue_vue_type_script_setup_true_lang.js +23 -0
- package/dist/components/CloseIcon/types.d.ts +2 -0
- package/dist/components/CommaBox/CommaBox.vue.d.ts +2 -1
- package/dist/components/CommaBox/CommaBox.vue.js +6 -0
- package/dist/components/CommaBox/CommaBox.vue_vue_type_script_setup_true_lang.js +81 -0
- package/dist/components/CommaBox/types.d.ts +6 -7
- package/dist/components/CommonInputWrapper/CommonInputWrapper.vue.d.ts +19 -0
- package/dist/components/CommonInputWrapper/CommonInputWrapper.vue.js +6 -0
- package/dist/components/CommonInputWrapper/CommonInputWrapper.vue_vue_type_script_setup_true_lang.js +107 -0
- package/dist/components/CommonInputWrapper/types.d.ts +96 -0
- package/dist/components/CommonPopover/CommonPopover.vue.d.ts +3 -3
- package/dist/components/CommonPopover/CommonPopover.vue.js +6 -0
- package/dist/components/CommonPopover/CommonPopover.vue_vue_type_script_setup_true_lang.js +160 -0
- package/dist/components/CommonPopover/types.d.ts +61 -16
- package/dist/components/ErrorIcon/ErrorIcon.vue.d.ts +2 -0
- package/dist/components/ErrorIcon/ErrorIcon.vue.js +6 -0
- package/dist/components/ErrorIcon/ErrorIcon.vue_vue_type_script_setup_true_lang.js +16 -0
- package/dist/components/FlatListBox/FlatListBox.vue.d.ts +2 -5
- package/dist/components/FlatListBox/FlatListBox.vue.js +5 -0
- package/dist/components/FlatListBox/FlatListBox.vue_vue_type_script_setup_true_lang.js +167 -0
- package/dist/components/FlatListBox/types.d.ts +1 -1
- package/dist/components/GroupedListBox/GroupedListBox.vue.d.ts +2 -5
- package/dist/components/GroupedListBox/GroupedListBox.vue.js +5 -0
- package/dist/components/GroupedListBox/GroupedListBox.vue_vue_type_script_setup_true_lang.js +218 -0
- package/dist/components/ListBox/ListBox.vue.d.ts +3 -6
- package/dist/components/ListBox/ListBox.vue.js +5 -0
- package/dist/components/ListBox/ListBox.vue_vue_type_script_setup_true_lang.js +112 -0
- package/dist/components/ListBox/types.d.ts +1 -1
- package/dist/components/OptionsContainer/OptionsContainer.vue.d.ts +1 -0
- package/dist/components/OptionsContainer/OptionsContainer.vue.js +5 -0
- package/dist/components/OptionsContainer/OptionsContainer.vue_vue_type_script_setup_true_lang.js +52 -0
- package/dist/components/OptionsContainer/types.d.ts +14 -24
- package/dist/components/Transitions/ScaleFade.vue.js +5 -0
- package/dist/components/Transitions/ScaleFade.vue_vue_type_script_setup_true_lang.js +47 -0
- package/dist/components/Transitions/Slide.vue.js +7 -0
- package/dist/components/Transitions/Slide.vue_vue_type_script_setup_true_lang.js +28 -0
- package/dist/components/WarningIcon/WarningIcon.vue.d.ts +5 -0
- package/dist/components/WarningIcon/WarningIcon.vue.js +6 -0
- package/dist/components/WarningIcon/WarningIcon.vue_vue_type_script_setup_true_lang.js +16 -0
- package/dist/components/WarningIcon/types.d.ts +3 -0
- package/dist/composables/__testResets.d.ts +1 -0
- package/dist/composables/useActiveInactiveState.d.ts +4 -0
- package/dist/composables/useActiveInactiveState.js +21 -0
- package/dist/composables/useBaseOptions.d.ts +3 -3
- package/dist/composables/useBaseOptions.js +31 -0
- package/dist/composables/useBbConfig.d.ts +8 -37
- package/dist/composables/useBbConfig.js +5 -0
- package/dist/composables/useBbDropdownContext.d.ts +89 -0
- package/dist/composables/useBbDropdownContext.js +46 -0
- package/dist/composables/useBbFormContext.d.ts +90 -0
- package/dist/composables/useBbFormContext.js +69 -0
- package/dist/composables/useBbTableContext.d.ts +195 -0
- package/dist/composables/useBbTableContext.js +208 -0
- package/dist/composables/useBbTabsContext.d.ts +111 -0
- package/dist/composables/useBbTabsContext.js +219 -0
- package/dist/composables/useBreakpoint.d.ts +13 -0
- package/dist/composables/useBreakpoint.js +39 -0
- package/dist/composables/useBroadcastChannelInstance.d.ts +1 -3
- package/dist/composables/useBroadcastChannelInstance.js +39 -0
- package/dist/composables/useCancelableFunction.js +37 -0
- package/dist/composables/useCoherence.js +16 -0
- package/dist/composables/useConfirm.d.ts +257 -67
- package/dist/composables/useConfirm.js +122 -0
- package/dist/composables/useCountdown.js +42 -0
- package/dist/composables/useHashedWatcher.d.ts +17 -0
- package/dist/composables/useHashedWatcher.js +14 -0
- package/dist/composables/useId.d.ts +3 -2
- package/dist/composables/useIndexById.js +16 -0
- package/dist/composables/useInertiaPage.d.ts +10 -0
- package/dist/composables/useInertiaPage.js +8 -0
- package/dist/composables/useItemText.js +17 -0
- package/dist/composables/useItemValue.js +9 -0
- package/dist/composables/useItemsGetter.d.ts +1 -1
- package/dist/composables/useItemsGetter.js +30 -0
- package/dist/composables/useListboxFocus.js +66 -0
- package/dist/composables/useLocale.d.ts +1 -1
- package/dist/composables/useLocale.js +11 -0
- package/dist/composables/useLogger.d.ts +1 -1
- package/dist/composables/useLogger.js +10 -0
- package/dist/composables/useMinimalQueue.d.ts +1 -1
- package/dist/composables/useMobile.js +7 -0
- package/dist/composables/useOnFalsy.d.ts +2 -0
- package/dist/composables/useOnFalsy.js +9 -0
- package/dist/composables/useOnTruthy.d.ts +7 -0
- package/dist/composables/useOnTruthy.js +9 -0
- package/dist/composables/usePrefill.d.ts +2 -1
- package/dist/composables/usePrefill.js +24 -0
- package/dist/composables/useQuery.d.ts +15 -3
- package/dist/composables/useQuery.js +42 -0
- package/dist/composables/useRoute.js +11 -0
- package/dist/composables/useRouter.js +8 -0
- package/dist/composables/useSafeArea.d.ts +51 -0
- package/dist/composables/useSafeArea.js +16 -0
- package/dist/composables/useSegmentedFields.d.ts +103 -0
- package/dist/composables/useSegmentedFields.js +152 -0
- package/dist/composables/useTableWidthContext.d.ts +5 -0
- package/dist/composables/useTableWidthContext.js +42 -0
- package/dist/composables/useToast.d.ts +17 -73
- package/dist/composables/useToast.js +49 -0
- package/dist/composables/useTopLayer.d.ts +9 -0
- package/dist/composables/useTopLayer.js +7 -0
- package/dist/composables/useUntil.js +13 -0
- package/dist/composables/useViewportWidth.d.ts +17 -0
- package/dist/composables/useViewportWidth.js +6 -0
- package/dist/confirm-variants.d.ts +10 -0
- package/dist/deprecation/ai-deprecations.json.d.ts +784 -0
- package/dist/deprecation/ai-deprecations.json.js +4 -0
- package/dist/deprecation/removedProps.d.ts +3 -0
- package/dist/deprecation/removedProps.js +13 -0
- package/dist/directives/bbColor.d.ts +65 -0
- package/dist/directives/bbColor.js +24 -0
- package/dist/directives/bbDate.d.ts +66 -0
- package/dist/directives/bbDate.js +24 -0
- package/dist/directives/bbDropdown.d.ts +15 -11
- package/dist/directives/bbDropdown.js +23 -0
- package/dist/directives/bbTime.d.ts +64 -0
- package/dist/directives/bbTime.js +24 -0
- package/dist/directives/bbTooltip.d.ts +16 -11
- package/dist/directives/bbTooltip.js +24 -0
- package/dist/dropdown-item-variants.d.ts +10 -0
- package/dist/i18n/index.d.ts +51 -3
- package/dist/i18n/index.js +27 -0
- package/dist/i18n/locales/cs.json +115 -0
- package/dist/i18n/locales/da.json +115 -0
- package/dist/i18n/locales/de.json +115 -0
- package/dist/i18n/locales/el.json +115 -0
- package/dist/i18n/locales/en.json +115 -0
- package/dist/i18n/locales/es.json +115 -0
- package/dist/i18n/locales/fi.json +115 -0
- package/dist/i18n/locales/fr.json +115 -0
- package/dist/i18n/locales/hu.json +115 -0
- package/dist/i18n/locales/it.json +115 -0
- package/dist/i18n/locales/ja.json +115 -0
- package/dist/i18n/locales/ko.json +115 -0
- package/dist/i18n/locales/nb.json +115 -0
- package/dist/i18n/locales/nl.json +115 -0
- package/dist/i18n/locales/pl.json +115 -0
- package/dist/i18n/locales/pt.json +115 -0
- package/dist/i18n/locales/ro.json +115 -0
- package/dist/i18n/locales/ru.json +115 -0
- package/dist/i18n/locales/sv.json +115 -0
- package/dist/i18n/locales/tr.json +115 -0
- package/dist/i18n/locales/uk.json +115 -0
- package/dist/i18n/locales/zh-cn.json +115 -0
- package/dist/icons-whitelist.d.ts +8 -0
- package/dist/index.d.ts +94 -79
- package/dist/index.js +76 -82
- package/dist/indicator-variants.d.ts +13 -0
- package/dist/llms-full.txt +77073 -0
- package/dist/llms-medium.txt +1334 -0
- package/dist/llms.txt +103 -0
- package/dist/locale-blueprint.json +141 -0
- package/dist/nuxt-module.d.ts +3 -0
- package/dist/nuxt.js +19 -0
- package/dist/plugin.d.ts +4 -0
- package/dist/plugin.js +22 -0
- package/dist/reset.css +1 -0
- package/dist/reset.d.ts +0 -0
- package/dist/runtime/nuxt-plugin.d.ts +2 -0
- package/dist/runtime/nuxt-plugin.js +24 -0
- package/dist/styles.css +2 -0
- package/dist/styles.d.ts +0 -0
- package/dist/toast-variants.d.ts +13 -0
- package/dist/tooltip-variants.d.ts +10 -0
- package/dist/types/ActionButton.d.ts +32 -0
- package/dist/types/AlertVariant.d.ts +16 -0
- package/dist/types/BadgeVariant.d.ts +16 -0
- package/dist/types/ButtonVariant.d.ts +17 -0
- package/dist/types/Classes.d.ts +1 -1
- package/dist/types/ColumnName.d.ts +1 -1
- package/dist/types/CommonProps.d.ts +66 -279
- package/dist/types/Config.d.ts +84 -0
- package/dist/types/ConfirmVariant.d.ts +15 -0
- package/dist/types/DropdownItemVariant.d.ts +18 -0
- package/dist/types/Icon.d.ts +5 -7
- package/dist/types/IndicatorVariant.d.ts +16 -0
- package/dist/types/InputDirection.d.ts +15 -0
- package/dist/types/ItemAccessor.d.ts +21 -0
- package/dist/types/Placement.d.ts +6 -0
- package/dist/types/ResponsiveSize.d.ts +20 -0
- package/dist/types/SizeScale.d.ts +8 -0
- package/dist/types/ToastVariant.d.ts +17 -0
- package/dist/types/TooltipVariant.d.ts +18 -0
- package/dist/utilities/functions/bytesToUnit.d.ts +2 -0
- package/dist/utilities/functions/bytesToUnit.js +18 -0
- package/dist/utilities/functions/clampPrecision.js +10 -0
- package/dist/utilities/functions/convertDateValue.d.ts +23 -10
- package/dist/utilities/functions/convertDateValue.js +12 -0
- package/dist/utilities/functions/deburr.js +5 -0
- package/dist/utilities/functions/empty.js +5 -0
- package/dist/utilities/functions/flattenTree.d.ts +1 -1
- package/dist/utilities/functions/hash.js +5 -0
- package/dist/utilities/functions/isDateDisabled.d.ts +1 -0
- package/dist/utilities/functions/isDateDisabled.js +20 -0
- package/dist/utilities/functions/isEqual.js +5 -0
- package/dist/utilities/functions/isNotEmpty.js +5 -0
- package/dist/utilities/functions/matchAnyKey.d.ts +1 -1
- package/dist/utilities/functions/matchAnyKey.js +15 -0
- package/dist/utilities/functions/parseSize.d.ts +1 -1
- package/dist/utilities/functions/parseSize.js +15 -0
- package/dist/utilities/functions/percentage.js +11 -0
- package/dist/utilities/functions/pipe.d.ts +1 -1
- package/dist/utilities/functions/reject.d.ts +25 -0
- package/dist/utilities/functions/reject.js +28 -0
- package/dist/utilities/functions/resolveEnumProp.d.ts +19 -0
- package/dist/utilities/functions/resolveEnumProp.js +5 -0
- package/dist/utilities/functions/slotKey.d.ts +13 -0
- package/dist/utilities/functions/slotKey.js +4 -0
- package/dist/utilities/functions/sort.d.ts +2 -2
- package/dist/utilities/functions/sort.js +19 -0
- package/dist/utilities/functions/throttle.js +5 -0
- package/dist/utilities/functions/waitFor.d.ts +9 -2
- package/dist/utilities/functions/waitFor.js +14 -0
- package/dist/utilities/functions/warnRemovedAttrs.d.ts +11 -0
- package/dist/utilities/functions/warnRemovedAttrs.js +20 -0
- package/dist/utilities/functions/when.js +5 -0
- package/dist/utilities/misc/iconsInjectionKey.d.ts +15 -0
- package/dist/utilities/misc/iconsInjectionKey.js +4 -0
- package/dist/utilities/misc/transitions.d.ts +5 -0
- package/dist/utilities/misc/transitions.js +8 -0
- package/dist/utils/injectLibraryStyles.d.ts +6 -0
- package/dist/utils/injectLibraryStyles.js +38 -0
- package/dist/validated/BbCheckbox.vue.d.ts +29 -0
- package/dist/validated/BbCheckbox.vue.js +5 -0
- package/dist/validated/BbCheckbox.vue_vue_type_script_setup_true_lang.js +75 -0
- package/dist/validated/BbCheckboxGroup.vue.d.ts +34 -0
- package/dist/validated/BbCheckboxGroup.vue.js +5 -0
- package/dist/validated/BbCheckboxGroup.vue_vue_type_script_setup_true_lang.js +76 -0
- package/dist/validated/BbColorInput.vue.d.ts +36 -0
- package/dist/validated/BbColorInput.vue.js +5 -0
- package/dist/validated/BbColorInput.vue_vue_type_script_setup_true_lang.js +71 -0
- package/dist/validated/BbDatePickerInput.vue.d.ts +40 -0
- package/dist/validated/BbDatePickerInput.vue.js +5 -0
- package/dist/validated/BbDatePickerInput.vue_vue_type_script_setup_true_lang.js +87 -0
- package/dist/validated/BbDropzone.vue.d.ts +24 -0
- package/dist/validated/BbDropzone.vue.js +5 -0
- package/dist/validated/BbDropzone.vue_vue_type_script_setup_true_lang.js +45 -0
- package/dist/validated/BbForm.vue.d.ts +28 -0
- package/dist/validated/BbForm.vue.js +5 -0
- package/dist/validated/BbForm.vue_vue_type_script_setup_true_lang.js +42 -0
- package/dist/validated/BbNumberInput.vue.d.ts +36 -0
- package/dist/validated/BbNumberInput.vue.js +5 -0
- package/dist/validated/BbNumberInput.vue_vue_type_script_setup_true_lang.js +72 -0
- package/dist/validated/BbRadioGroup.vue.d.ts +34 -0
- package/dist/validated/BbRadioGroup.vue.js +5 -0
- package/dist/validated/BbRadioGroup.vue_vue_type_script_setup_true_lang.js +75 -0
- package/dist/validated/BbRating.vue.d.ts +30 -0
- package/dist/validated/BbRating.vue.js +5 -0
- package/dist/validated/BbRating.vue_vue_type_script_setup_true_lang.js +63 -0
- package/dist/validated/BbSelect.vue.d.ts +46 -0
- package/dist/validated/BbSelect.vue.js +5 -0
- package/dist/validated/BbSelect.vue_vue_type_script_setup_true_lang.js +100 -0
- package/dist/validated/BbSlider.vue.d.ts +36 -0
- package/dist/validated/BbSlider.vue.js +5 -0
- package/dist/validated/BbSlider.vue_vue_type_script_setup_true_lang.js +69 -0
- package/dist/validated/BbSwitch.vue.d.ts +29 -0
- package/dist/validated/BbSwitch.vue.js +5 -0
- package/dist/validated/BbSwitch.vue_vue_type_script_setup_true_lang.js +75 -0
- package/dist/validated/BbSwitchGroup.vue.d.ts +34 -0
- package/dist/validated/BbSwitchGroup.vue.js +5 -0
- package/dist/validated/BbSwitchGroup.vue_vue_type_script_setup_true_lang.js +76 -0
- package/dist/validated/BbTag.vue.d.ts +36 -0
- package/dist/validated/BbTag.vue.js +5 -0
- package/dist/validated/BbTag.vue_vue_type_script_setup_true_lang.js +72 -0
- package/dist/validated/BbTextInput.vue.d.ts +36 -0
- package/dist/validated/BbTextInput.vue.js +5 -0
- package/dist/validated/BbTextInput.vue_vue_type_script_setup_true_lang.js +72 -0
- package/dist/validated/BbTextarea.vue.d.ts +36 -0
- package/dist/validated/BbTextarea.vue.js +5 -0
- package/dist/validated/BbTextarea.vue_vue_type_script_setup_true_lang.js +70 -0
- package/dist/validated/BbTimePickerInput.vue.d.ts +36 -0
- package/dist/validated/BbTimePickerInput.vue.js +5 -0
- package/dist/validated/BbTimePickerInput.vue_vue_type_script_setup_true_lang.js +78 -0
- package/dist/validated/index.d.ts +29 -0
- package/dist/validated/useValidatedField.d.ts +52 -0
- package/dist/validated/useValidatedField.js +51 -0
- package/dist/validated.js +21 -0
- package/dist/vite-plugin.d.ts +217 -0
- package/dist/vite.js +530 -0
- package/llms.txt +105 -0
- package/package.json +161 -59
- package/scripts/lib/__fixtures__/validated-manifest.json +65 -0
- package/scripts/lib/ensure-manifest.mjs +39 -0
- package/scripts/lib/eslint-plugin-autofix.node.test.mjs +109 -0
- package/scripts/lib/eslint-plugin.mjs +285 -0
- package/scripts/lib/eslint-plugin.node.test.mjs +161 -0
- package/scripts/lib/html-attributes.mjs +191 -0
- package/scripts/lib/mcp-config.mjs +126 -0
- package/scripts/lib/migrate-fix.mjs +115 -0
- package/scripts/lib/validate-bb-markup.mjs +421 -0
- package/dist/ai/BaseButton.md +0 -449
- package/dist/ai/BaseCheckbox.md +0 -512
- package/dist/ai/BaseCheckboxGroup.md +0 -597
- package/dist/ai/BaseColorInput.md +0 -461
- package/dist/ai/BaseDatePicker.md +0 -739
- package/dist/ai/BaseDatePickerInput.md +0 -1517
- package/dist/ai/BaseDialog.md +0 -610
- package/dist/ai/BaseInputContainer.md +0 -570
- package/dist/ai/BaseNumberInput.md +0 -509
- package/dist/ai/BaseRadio.md +0 -405
- package/dist/ai/BaseRadioGroup.md +0 -535
- package/dist/ai/BaseRating.md +0 -489
- package/dist/ai/BaseSelect.md +0 -1742
- package/dist/ai/BaseSlider.md +0 -871
- package/dist/ai/BaseSwitch.md +0 -322
- package/dist/ai/BaseSwitchGroup.md +0 -298
- package/dist/ai/BaseTag.md +0 -623
- package/dist/ai/BaseTextInput.md +0 -392
- package/dist/ai/BaseTextarea.md +0 -398
- package/dist/ai/BbChip.md +0 -147
- package/dist/ai/BbIntersection.md +0 -120
- package/dist/ai/BbRatio.md +0 -61
- package/dist/ai/BbRows.md +0 -307
- package/dist/ai/BbTab.md +0 -950
- package/dist/ai/CommonInputInnerContainer.md +0 -414
- package/dist/ai/CommonInputOuterContainer.md +0 -55
- package/dist/components/BaseButton/BaseButton.vue.d.ts +0 -14
- package/dist/components/BaseButton/types.d.ts +0 -159
- package/dist/components/BaseCheckbox/BaseCheckbox.vue.d.ts +0 -39
- package/dist/components/BaseCheckbox/types.d.ts +0 -213
- package/dist/components/BaseCheckboxGroup/BaseCheckboxGroup.vue.d.ts +0 -30
- package/dist/components/BaseCheckboxGroup/types.d.ts +0 -282
- package/dist/components/BaseColorInput/BaseColorInput.vue.d.ts +0 -35
- package/dist/components/BaseColorInput/ColorPalette.vue.d.ts +0 -13
- package/dist/components/BaseColorInput/types.d.ts +0 -233
- package/dist/components/BaseDatePicker/BaseDatePickerHeader.vue.d.ts +0 -15
- package/dist/components/BaseDatePicker/BaseDatePickerInputDaySelector.vue.d.ts +0 -58
- package/dist/components/BaseDatePicker/BaseDatePickerMonthSelector.vue.d.ts +0 -12
- package/dist/components/BaseDatePicker/BaseDatePickerYearSelector.vue.d.ts +0 -12
- package/dist/components/BaseDatePicker/types.d.ts +0 -100
- package/dist/components/BaseDatePickerInput/BaseDatePickerInput.vue.d.ts +0 -26
- package/dist/components/BaseDatePickerInput/types.d.ts +0 -206
- package/dist/components/BaseDialog/BaseDialog.vue.d.ts +0 -37
- package/dist/components/BaseDialog/types.d.ts +0 -180
- package/dist/components/BaseInputContainer/BaseInputContainer.vue.d.ts +0 -18
- package/dist/components/BaseInputContainer/types.d.ts +0 -126
- package/dist/components/BaseNumberInput/BaseNumberInput.vue.d.ts +0 -46
- package/dist/components/BaseNumberInput/types.d.ts +0 -191
- package/dist/components/BaseRadio/BaseRadio.vue.d.ts +0 -31
- package/dist/components/BaseRadio/types.d.ts +0 -173
- package/dist/components/BaseRadioGroup/BaseRadioGroup.vue.d.ts +0 -30
- package/dist/components/BaseRadioGroup/types.d.ts +0 -240
- package/dist/components/BaseRating/BaseRating.vue.d.ts +0 -35
- package/dist/components/BaseRating/types.d.ts +0 -144
- package/dist/components/BaseSelect/BaseSelect.vue.d.ts +0 -28
- package/dist/components/BaseSelect/types.d.ts +0 -470
- package/dist/components/BaseSlider/BaseSlider.vue.d.ts +0 -38
- package/dist/components/BaseSlider/types.d.ts +0 -201
- package/dist/components/BaseSwitch/BaseSwitch.vue.d.ts +0 -34
- package/dist/components/BaseSwitch/types.d.ts +0 -25
- package/dist/components/BaseSwitchGroup/BaseSwitchGroup.vue.d.ts +0 -30
- package/dist/components/BaseSwitchGroup/types.d.ts +0 -8
- package/dist/components/BaseTag/BaseTag.vue.d.ts +0 -37
- package/dist/components/BaseTag/types.d.ts +0 -136
- package/dist/components/BaseTextInput/BaseTextInput.vue.d.ts +0 -33
- package/dist/components/BaseTextInput/types.d.ts +0 -166
- package/dist/components/BaseTextarea/BaseTextarea.vue.d.ts +0 -31
- package/dist/components/BaseTextarea/types.d.ts +0 -151
- package/dist/components/BbChip/BbChip.vue.d.ts +0 -17
- package/dist/components/BbChip/types.d.ts +0 -23
- package/dist/components/BbIntersection/BbIntersection.vue.d.ts +0 -23
- package/dist/components/BbIntersection/types.d.ts +0 -36
- package/dist/components/BbRatio/BbRatio.vue.d.ts +0 -14
- package/dist/components/BbRatio/types.d.ts +0 -15
- package/dist/components/BbRows/BbRows.vue.d.ts +0 -62
- package/dist/components/BbRows/types.d.ts +0 -21
- package/dist/components/BbTab/BbTab.vue.d.ts +0 -27
- package/dist/components/BbTab/types.d.ts +0 -186
- package/dist/components/CommonInputInnerContainer/CommonInputInnerContainer.vue.d.ts +0 -15
- package/dist/components/CommonInputInnerContainer/types.d.ts +0 -47
- package/dist/components/CommonInputOuterContainer/CommonInputOuterContainer.vue.d.ts +0 -11
- package/dist/components/CommonInputOuterContainer/types.d.ts +0 -16
- package/dist/composables/useModalsState.d.ts +0 -5
- package/dist/composables/useQueue.d.ts +0 -58
- package/dist/composables/useWizard.d.ts +0 -85
- package/dist/i18n/en.d.ts +0 -67
- package/dist/i18n/it.d.ts +0 -67
- package/dist/index.css +0 -2
- package/dist/index100.js +0 -6
- package/dist/index101.js +0 -300
- package/dist/index102.js +0 -16
- package/dist/index103.js +0 -16
- package/dist/index104.js +0 -27
- package/dist/index109.js +0 -19
- package/dist/index11.js +0 -42
- package/dist/index110.js +0 -12
- package/dist/index111.js +0 -19
- package/dist/index112.js +0 -25
- package/dist/index114.js +0 -7
- package/dist/index115.js +0 -241
- package/dist/index116.js +0 -5
- package/dist/index117.js +0 -42
- package/dist/index118.js +0 -5
- package/dist/index119.js +0 -53
- package/dist/index120.js +0 -5
- package/dist/index121.js +0 -32
- package/dist/index122.js +0 -5
- package/dist/index124.js +0 -52
- package/dist/index125.js +0 -5
- package/dist/index126.js +0 -223
- package/dist/index128.js +0 -6
- package/dist/index129.js +0 -162
- package/dist/index13.js +0 -92
- package/dist/index130.js +0 -25
- package/dist/index131.js +0 -51
- package/dist/index132.js +0 -5
- package/dist/index133.js +0 -607
- package/dist/index135.js +0 -6
- package/dist/index136.js +0 -14
- package/dist/index137.js +0 -153
- package/dist/index139.js +0 -6
- package/dist/index14.js +0 -306
- package/dist/index140.js +0 -224
- package/dist/index141.js +0 -5
- package/dist/index143.js +0 -128
- package/dist/index145.js +0 -6
- package/dist/index146.js +0 -8
- package/dist/index147.js +0 -182
- package/dist/index149.js +0 -6
- package/dist/index15.js +0 -10
- package/dist/index151.js +0 -4
- package/dist/index152.js +0 -95
- package/dist/index154.js +0 -6
- package/dist/index155.js +0 -168
- package/dist/index157.js +0 -6
- package/dist/index158.js +0 -145
- package/dist/index16.js +0 -59
- package/dist/index160.js +0 -6
- package/dist/index161.js +0 -53
- package/dist/index163.js +0 -6
- package/dist/index164.js +0 -16
- package/dist/index166.js +0 -35
- package/dist/index168.js +0 -6
- package/dist/index169.js +0 -89
- package/dist/index171.js +0 -6
- package/dist/index172.js +0 -82
- package/dist/index174.js +0 -6
- package/dist/index175.js +0 -38
- package/dist/index176.js +0 -461
- package/dist/index177.js +0 -28
- package/dist/index178.js +0 -66
- package/dist/index18.js +0 -35
- package/dist/index180.js +0 -162
- package/dist/index181.js +0 -5
- package/dist/index182.js +0 -217
- package/dist/index183.js +0 -5
- package/dist/index184.js +0 -112
- package/dist/index185.js +0 -5
- package/dist/index186.js +0 -459
- package/dist/index188.js +0 -6
- package/dist/index19.js +0 -4
- package/dist/index192.js +0 -268
- package/dist/index194.js +0 -6
- package/dist/index195.js +0 -124
- package/dist/index197.js +0 -6
- package/dist/index198.js +0 -174
- package/dist/index2.js +0 -20
- package/dist/index20.js +0 -9
- package/dist/index200.js +0 -6
- package/dist/index201.js +0 -224
- package/dist/index203.js +0 -6
- package/dist/index204.js +0 -135
- package/dist/index206.js +0 -6
- package/dist/index207.js +0 -5
- package/dist/index208.js +0 -170
- package/dist/index21.js +0 -35
- package/dist/index210.js +0 -6
- package/dist/index211.js +0 -37
- package/dist/index213.js +0 -6
- package/dist/index214.js +0 -63
- package/dist/index216.js +0 -6
- package/dist/index217.js +0 -74
- package/dist/index219.js +0 -6
- package/dist/index22.js +0 -6
- package/dist/index220.js +0 -65
- package/dist/index222.js +0 -6
- package/dist/index223.js +0 -67
- package/dist/index225.js +0 -6
- package/dist/index226.js +0 -41
- package/dist/index228.js +0 -6
- package/dist/index229.js +0 -10
- package/dist/index23.js +0 -7
- package/dist/index230.js +0 -22
- package/dist/index232.js +0 -8
- package/dist/index233.js +0 -297
- package/dist/index235.js +0 -6
- package/dist/index236.js +0 -47
- package/dist/index237.js +0 -165
- package/dist/index239.js +0 -6
- package/dist/index24.js +0 -9
- package/dist/index241.js +0 -159
- package/dist/index243.js +0 -6
- package/dist/index244.js +0 -134
- package/dist/index246.js +0 -6
- package/dist/index247.js +0 -167
- package/dist/index249.js +0 -6
- package/dist/index25.js +0 -24
- package/dist/index250.js +0 -147
- package/dist/index252.js +0 -6
- package/dist/index253.js +0 -35
- package/dist/index255.js +0 -6
- package/dist/index256.js +0 -65
- package/dist/index258.js +0 -6
- package/dist/index259.js +0 -19
- package/dist/index260.js +0 -5
- package/dist/index261.js +0 -160
- package/dist/index263.js +0 -6
- package/dist/index264.js +0 -60
- package/dist/index266.js +0 -6
- package/dist/index267.js +0 -134
- package/dist/index269.js +0 -6
- package/dist/index27.js +0 -4
- package/dist/index271.js +0 -206
- package/dist/index273.js +0 -6
- package/dist/index274.js +0 -85
- package/dist/index275.js +0 -5
- package/dist/index276.js +0 -55
- package/dist/index277.js +0 -5
- package/dist/index278.js +0 -164
- package/dist/index28.js +0 -8
- package/dist/index280.js +0 -6
- package/dist/index281.js +0 -145
- package/dist/index283.js +0 -6
- package/dist/index284.js +0 -11
- package/dist/index285.js +0 -200
- package/dist/index287.js +0 -6
- package/dist/index288.js +0 -241
- package/dist/index29.js +0 -7
- package/dist/index290.js +0 -6
- package/dist/index291.js +0 -21
- package/dist/index293.js +0 -6
- package/dist/index294.js +0 -125
- package/dist/index296.js +0 -6
- package/dist/index297.js +0 -161
- package/dist/index299.js +0 -6
- package/dist/index30.js +0 -118
- package/dist/index300.js +0 -127
- package/dist/index302.js +0 -6
- package/dist/index303.js +0 -20
- package/dist/index305.js +0 -6
- package/dist/index306.js +0 -116
- package/dist/index307.js +0 -5
- package/dist/index308.js +0 -507
- package/dist/index31.js +0 -10
- package/dist/index310.js +0 -6
- package/dist/index311.js +0 -196
- package/dist/index313.js +0 -6
- package/dist/index314.js +0 -142
- package/dist/index316.js +0 -6
- package/dist/index317.js +0 -134
- package/dist/index319.js +0 -6
- package/dist/index32.js +0 -28
- package/dist/index320.js +0 -162
- package/dist/index322.js +0 -6
- package/dist/index323.js +0 -319
- package/dist/index325.js +0 -6
- package/dist/index326.js +0 -40
- package/dist/index327.js +0 -43
- package/dist/index328.js +0 -665
- package/dist/index33.js +0 -5
- package/dist/index330.js +0 -6
- package/dist/index331.js +0 -155
- package/dist/index333.js +0 -6
- package/dist/index334.js +0 -142
- package/dist/index336.js +0 -6
- package/dist/index337.js +0 -155
- package/dist/index339.js +0 -6
- package/dist/index34.js +0 -112
- package/dist/index340.js +0 -58
- package/dist/index341.js +0 -5
- package/dist/index342.js +0 -20
- package/dist/index344.js +0 -6
- package/dist/index345.js +0 -142
- package/dist/index347.js +0 -6
- package/dist/index349.js +0 -109
- package/dist/index351.js +0 -6
- package/dist/index353.js +0 -24
- package/dist/index354.js +0 -23
- package/dist/index355.js +0 -839
- package/dist/index356.js +0 -474
- package/dist/index357.js +0 -52
- package/dist/index358.js +0 -7
- package/dist/index36.js +0 -6
- package/dist/index38.js +0 -63
- package/dist/index39.js +0 -63
- package/dist/index40.js +0 -9
- package/dist/index42.js +0 -8
- package/dist/index43.js +0 -179
- package/dist/index44.js +0 -21
- package/dist/index46.js +0 -385
- package/dist/index48.js +0 -6
- package/dist/index49.js +0 -33
- package/dist/index5.js +0 -34
- package/dist/index50.js +0 -12
- package/dist/index51.js +0 -8
- package/dist/index52.js +0 -106
- package/dist/index53.js +0 -330
- package/dist/index54.js +0 -97
- package/dist/index55.js +0 -328
- package/dist/index56.js +0 -28
- package/dist/index57.js +0 -102
- package/dist/index58.js +0 -157
- package/dist/index6.js +0 -38
- package/dist/index60.js +0 -6
- package/dist/index61.js +0 -131
- package/dist/index62.js +0 -5
- package/dist/index65.js +0 -35
- package/dist/index66.js +0 -26
- package/dist/index68.js +0 -6
- package/dist/index69.js +0 -54
- package/dist/index7.js +0 -58
- package/dist/index71.js +0 -6
- package/dist/index72.js +0 -14
- package/dist/index74.js +0 -6
- package/dist/index75.js +0 -159
- package/dist/index77.js +0 -6
- package/dist/index78.js +0 -4
- package/dist/index79.js +0 -4
- package/dist/index80.js +0 -54
- package/dist/index81.js +0 -5
- package/dist/index82.js +0 -5
- package/dist/index83.js +0 -141
- package/dist/index85.js +0 -6
- package/dist/index86.js +0 -65
- package/dist/index88.js +0 -6
- package/dist/index89.js +0 -30
- package/dist/index9.js +0 -11
- package/dist/index90.js +0 -19
- package/dist/index91.js +0 -17
- package/dist/index92.js +0 -9
- package/dist/index93.js +0 -34
- package/dist/index95.js +0 -16
- package/dist/index96.js +0 -10
- package/dist/index97.js +0 -18
- package/dist/index98.js +0 -189
- package/dist/types/VoidHtmlElements.d.ts +0 -1
- package/dist/utilities/functions/add.d.ts +0 -1
- package/dist/utilities/functions/booleanishToBoolean.d.ts +0 -6
- package/dist/utilities/functions/capitalize.d.ts +0 -4
- package/dist/utilities/functions/cloneDeep.d.ts +0 -1
- package/dist/utilities/functions/debounce.d.ts +0 -1
- package/dist/utilities/functions/drop.d.ts +0 -1
- package/dist/utilities/functions/exceedsPrecision.d.ts +0 -1
- package/dist/utilities/functions/findRight.d.ts +0 -4
- package/dist/utilities/functions/findRightIndex.d.ts +0 -4
- package/dist/utilities/functions/flow.d.ts +0 -1
- package/dist/utilities/functions/getHueFromRGB.d.ts +0 -9
- package/dist/utilities/functions/groupBy.d.ts +0 -6
- package/dist/utilities/functions/gt.d.ts +0 -7
- package/dist/utilities/functions/hexToRGB.d.ts +0 -7
- package/dist/utilities/functions/hslToHex.d.ts +0 -1
- package/dist/utilities/functions/includes.d.ts +0 -1
- package/dist/utilities/functions/initial.d.ts +0 -1
- package/dist/utilities/functions/isCssColor.d.ts +0 -5
- package/dist/utilities/functions/lt.d.ts +0 -7
- package/dist/utilities/functions/makeUrl.d.ts +0 -1
- package/dist/utilities/functions/mapKeys.d.ts +0 -5
- package/dist/utilities/functions/mapValues.d.ts +0 -1
- package/dist/utilities/functions/matchesPropertyInsensitive.d.ts +0 -1
- package/dist/utilities/functions/max.d.ts +0 -2
- package/dist/utilities/functions/mean.d.ts +0 -4
- package/dist/utilities/functions/merge.d.ts +0 -11
- package/dist/utilities/functions/millisecondsTo.d.ts +0 -1
- package/dist/utilities/functions/negate.d.ts +0 -4
- package/dist/utilities/functions/normalizeItemGroups.d.ts +0 -13
- package/dist/utilities/functions/omit.d.ts +0 -2
- package/dist/utilities/functions/overEvery.d.ts +0 -4
- package/dist/utilities/functions/pick.d.ts +0 -1
- package/dist/utilities/functions/replace.d.ts +0 -1
- package/dist/utilities/functions/replaceInTree.d.ts +0 -4
- package/dist/utilities/functions/retry.d.ts +0 -5
- package/dist/utilities/functions/roundToTheNearest.d.ts +0 -8
- package/dist/utilities/functions/set.d.ts +0 -15
- package/dist/utilities/functions/stub.d.ts +0 -6
- package/dist/utilities/functions/transposePercent.d.ts +0 -9
- package/dist/utilities/functions/tryTo.d.ts +0 -2
- package/dist/utilities/functions/uniqBy.d.ts +0 -1
- /package/dist/{index64.js → _virtual/_plugin-vue_export-helper.js} +0 -0
- /package/dist/components/{BaseButton → BbBaseButton}/RouterComponent.vue.d.ts +0 -0
- /package/dist/components/{BaseDatePicker/BaseDatePicker.vue.d.ts → BbBaseDatePicker/BbBaseDatePicker.vue.d.ts} +0 -0
- /package/dist/{index165.js → composables/useArray.js} +0 -0
- /package/dist/{index17.js → composables/useFrameworkDetection.js} +0 -0
- /package/dist/{index12.js → composables/useId.js} +0 -0
- /package/dist/{index352.js → directives/createPopoverDirective.js} +0 -0
- /package/dist/{index107.js → utilities/functions/chunk.js} +0 -0
- /package/dist/{index8.js → utilities/functions/clamp.js} +0 -0
- /package/dist/{index150.js → utilities/functions/curry.js} +0 -0
- /package/dist/{index45.js → utilities/functions/extractDomContainer.js} +0 -0
- /package/dist/{index270.js → utilities/functions/findInTree.js} +0 -0
- /package/dist/{index348.js → utilities/functions/flattenTree.js} +0 -0
- /package/dist/{index41.js → utilities/functions/get.js} +0 -0
- /package/dist/{index189.js → utilities/functions/head.js} +0 -0
- /package/dist/{index108.js → utilities/functions/identity.js} +0 -0
- /package/dist/{index179.js → utilities/functions/indexBy.js} +0 -0
- /package/dist/{index37.js → utilities/functions/isHex.js} +0 -0
- /package/dist/{index94.js → utilities/functions/isNil.js} +0 -0
- /package/dist/{index26.js → utilities/functions/isNotNil.js} +0 -0
- /package/dist/{index105.js → utilities/functions/last.js} +0 -0
- /package/dist/{index142.js → utilities/functions/noop.js} +0 -0
- /package/dist/{index231.js → utilities/functions/parseWidthString.js} +0 -0
- /package/dist/{index240.js → utilities/functions/pickBy.js} +0 -0
- /package/dist/{index123.js → utilities/functions/range.js} +0 -0
- /package/dist/{index190.js → utilities/functions/snapToStep.js} +0 -0
- /package/dist/{index191.js → utilities/functions/transpose.js} +0 -0
- /package/dist/{index106.js → utilities/functions/validateDateFormat.js} +0 -0
- /package/dist/{index10.js → utilities/functions/wait.js} +0 -0
|
@@ -0,0 +1,1486 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Wizard Form (Nuxt)
|
|
3
|
+
summary: Mailchimp-grade 4-step campaign wizard for Nuxt — BbBaseButton stepper with done/current/upcoming/locked states, per-step validation gating Next behind a BbAlert summary, BbSmoothHeight step swaps, the step mirrored into the URL with navigateTo, useAsyncData + FetchError 422 handling merged into the same validation summary, save-draft everywhere and a dirty route-leave guard, all through one coopting page context.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Recipe: Wizard Form (Nuxt)
|
|
7
|
+
|
|
8
|
+
The same Mailchimp-grade "Create campaign" wizard adapted for **Nuxt**: four
|
|
9
|
+
steps (Audience → Content → Schedule → Review) behind a custom stepper
|
|
10
|
+
header, per-step validation gating forward progress, the active step
|
|
11
|
+
mirrored into the URL, save-draft everywhere, and a dirty-state leave guard.
|
|
12
|
+
Anatomy, components, the context shape, copy and data are identical to the
|
|
13
|
+
Vue recipe; only the data/URL/mutation layer changes — the draft loads
|
|
14
|
+
through `useAsyncData`, mutations go through `$fetch` with `FetchError` 422
|
|
15
|
+
errors merged into the same per-step validation summary, and the step
|
|
16
|
+
mirrors into the URL with `navigateTo`.
|
|
17
|
+
|
|
18
|
+
Sibling recipes: [Vue](../vue/wizard-form.md) ·
|
|
19
|
+
[Inertia](../inertia/wizard-form.md).
|
|
20
|
+
|
|
21
|
+
## Prerequisites
|
|
22
|
+
|
|
23
|
+
- `<BbConfirm />` and `<BbToast />` mounted once in `app.vue`.
|
|
24
|
+
- [App Layout](../../guides/app-layout.md) and
|
|
25
|
+
[Page Shell](../../guides/page-shell.md) copied into
|
|
26
|
+
`src/components/layout/` per their guides — the page renders inside this
|
|
27
|
+
chrome.
|
|
28
|
+
- Server routes: `GET /api/campaigns/:id` (returns `{ campaign, draft }`),
|
|
29
|
+
`PATCH /api/campaigns/:id` (save draft — `422` with
|
|
30
|
+
`{ errors: { field: ['message'] } }` on validation failure),
|
|
31
|
+
`POST /api/campaigns/:id/send-test`, `POST /api/campaigns/:id/send`,
|
|
32
|
+
`DELETE /api/campaigns/:id`; plus `GET /api/segments/:id`,
|
|
33
|
+
`GET /api/segments/:id/estimate`, `GET /api/segments` (query-aware
|
|
34
|
+
search) and `GET /api/segments/:id/detail` for the peek.
|
|
35
|
+
|
|
36
|
+
## Key ideas
|
|
37
|
+
|
|
38
|
+
- **One coopting page context.** `provideCampaignWizard()` joins the
|
|
39
|
+
page-shell stack once and owns the draft, the current step, the live
|
|
40
|
+
validation, and every action. All seven leaves call
|
|
41
|
+
`useCampaignWizard()` and nothing else — the only prop on the page is
|
|
42
|
+
the id the segment peek renders.
|
|
43
|
+
- **The stepper is derived state, not navigation chrome.** A step is
|
|
44
|
+
reachable exactly when every step before it validates — the same
|
|
45
|
+
predicate `goNext()` uses — so jumping from the header can never outrun
|
|
46
|
+
the validation gate. Steps already passed show `done` (checkmarked),
|
|
47
|
+
reachable future steps stay numbered (`upcoming`), and `locked` steps
|
|
48
|
+
render as disabled `BbBaseButton`s.
|
|
49
|
+
- **Validation is computed live and merges server errors.** `issues`
|
|
50
|
+
recomputes on every keystroke from client-side checks _and_ any `422`
|
|
51
|
+
field errors left over from the last save, so a failed save surfaces on
|
|
52
|
+
the owning step exactly like a blocked Next. The `BbAlert` summary and
|
|
53
|
+
the inline field errors still appear only after a blocked Next
|
|
54
|
+
(`attempted`), and shrink live as each problem clears.
|
|
55
|
+
- **The step lives in the URL, the draft lives on the server.** `?step=3`
|
|
56
|
+
is mirrored with `navigateTo({ query }, { replace: true })`; on refresh
|
|
57
|
+
`useAsyncData` re-fetches the saved draft and the requested step is
|
|
58
|
+
clamped to the first incomplete one, so a shared link can never open a
|
|
59
|
+
locked step.
|
|
60
|
+
- **One `BbSmoothHeight` wraps the alert + step outlet together**, so the
|
|
61
|
+
step swap _and_ the validation summary appearing both glide instead of
|
|
62
|
+
snapping. Conditional regions inside a step (the scheduled-date fields)
|
|
63
|
+
need no wrapper of their own — the same resize observer catches them.
|
|
64
|
+
- **Back and Save draft are always free; validation only guards forward.**
|
|
65
|
+
The page's one `variant="primary"` is the final Send/Schedule submit,
|
|
66
|
+
with `:loading` riding `ctx.submitting`.
|
|
67
|
+
- **Leaving is guarded by a snapshot compare.** `dirty` is a JSON diff
|
|
68
|
+
against the last saved draft; `onBeforeRouteLeave` (works unchanged in
|
|
69
|
+
Nuxt) + `useConfirm` ask before losing changes, and both submit and
|
|
70
|
+
discard reset the snapshot so they never double-prompt.
|
|
71
|
+
|
|
72
|
+
## Page anatomy
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
CampaignWizard/
|
|
76
|
+
Index.vue ← assembly: PageShell slots, stepper, BbSmoothHeight outlet, footer
|
|
77
|
+
context.ts ← coopting page context (shell + draft + step + validation + actions)
|
|
78
|
+
StepperHeader.vue ← BbBaseButton steps with done/current/upcoming/locked states
|
|
79
|
+
AudienceStep.vue ← segment select, audience filters, live recipient estimate
|
|
80
|
+
ContentStep.vue ← subject/preheader, body, template picker, send-test
|
|
81
|
+
ScheduleStep.vue ← send-now vs scheduled, date + timezone
|
|
82
|
+
ReviewStep.vue ← read-only summary with per-section Edit links
|
|
83
|
+
WizardFooter.vue ← Back · Save draft · Next / Send with :loading
|
|
84
|
+
SegmentPeek.vue ← sidebar workbench for the selected segment
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Chrome assumed from the guides: [App Layout](../../guides/app-layout.md)
|
|
88
|
+
(left nav + fat footer) and [Page Shell](../../guides/page-shell.md).
|
|
89
|
+
Wizard archetype: no filter rail, no tabs, no table, no pagination, no bulk
|
|
90
|
+
bar — the work surface is the step outlet.
|
|
91
|
+
|
|
92
|
+
## Action inventory
|
|
93
|
+
|
|
94
|
+
| # | Action | Trigger | Mechanism |
|
|
95
|
+
| --- | --------------------------- | -------------------------------- | ------------------------------------------------------------------------ |
|
|
96
|
+
| 1 | Advance a step (validated) | footer **Next** (secondary) | `goNext()` — blocks and raises the `BbAlert` summary on issues |
|
|
97
|
+
| 2 | Go back | footer **Back** (ghost) | `goBack()` — never validated |
|
|
98
|
+
| 3 | Jump to an unlocked step | stepper header | `goToStep()`; locked steps are disabled `BbBaseButton`s |
|
|
99
|
+
| 4 | Edit a section from Review | per-section **Edit** links | `goToStep()` back to that step |
|
|
100
|
+
| 5 | Save draft | footer ghost, on every step | `saveDraft()` → `$fetch` PATCH + snapshot reset + toast |
|
|
101
|
+
| 6 | Send a test email | Content step ghost | `sendTest()` → `$fetch` POST, validates content first |
|
|
102
|
+
| 7 | Send / schedule campaign | Review, the page's one `primary` | `submit()` with `:loading`, whole-wizard validation, `$fetch` PATCH+POST |
|
|
103
|
+
| 8 | Discard draft | topbar, destructive | `useConfirm` → `$fetch` DELETE + `navigateTo('/campaigns')` |
|
|
104
|
+
| 9 | Peek the segment | segment name (Audience & Review) | `peekSegment()` → shell stack |
|
|
105
|
+
| 10 | Leave with unsaved changes | any route navigation | `onBeforeRouteLeave` guard → `useConfirm` |
|
|
106
|
+
| 11 | Restore position on refresh | URL `?step=N` | step mirrored via `navigateTo(..., { replace: true })`, clamped on load |
|
|
107
|
+
|
|
108
|
+
## The code
|
|
109
|
+
|
|
110
|
+
### context.ts
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
// CampaignWizard/context.ts
|
|
114
|
+
import {
|
|
115
|
+
computed,
|
|
116
|
+
inject,
|
|
117
|
+
provide,
|
|
118
|
+
reactive,
|
|
119
|
+
ref,
|
|
120
|
+
watch,
|
|
121
|
+
watchEffect,
|
|
122
|
+
type ComputedRef,
|
|
123
|
+
type InjectionKey,
|
|
124
|
+
type Ref,
|
|
125
|
+
} from 'vue';
|
|
126
|
+
import { onBeforeRouteLeave } from 'vue-router';
|
|
127
|
+
import { FetchError } from 'ofetch';
|
|
128
|
+
import { useConfirm, useToast } from 'bitboss-ui';
|
|
129
|
+
import {
|
|
130
|
+
usePageShellContext,
|
|
131
|
+
type PageShellContext,
|
|
132
|
+
} from '@/components/layout/usePageShellContext';
|
|
133
|
+
import type { Campaign, Segment } from '@/types';
|
|
134
|
+
|
|
135
|
+
/** What the sidebar can show: the audience segment behind the campaign. */
|
|
136
|
+
export type PeekItem = { type: 'segment'; id: number };
|
|
137
|
+
|
|
138
|
+
export type StepKey = 'audience' | 'content' | 'schedule' | 'review';
|
|
139
|
+
export type StepStatus = 'done' | 'current' | 'upcoming' | 'locked';
|
|
140
|
+
export type SendMode = 'now' | 'scheduled';
|
|
141
|
+
|
|
142
|
+
export const STEPS: { key: StepKey; label: string }[] = [
|
|
143
|
+
{ key: 'audience', label: 'Audience' },
|
|
144
|
+
{ key: 'content', label: 'Content' },
|
|
145
|
+
{ key: 'schedule', label: 'Schedule' },
|
|
146
|
+
{ key: 'review', label: 'Review' },
|
|
147
|
+
];
|
|
148
|
+
|
|
149
|
+
export interface CampaignTemplate {
|
|
150
|
+
id: string;
|
|
151
|
+
name: string;
|
|
152
|
+
description: string;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export const TEMPLATES: CampaignTemplate[] = [
|
|
156
|
+
{
|
|
157
|
+
id: 'product-update',
|
|
158
|
+
name: 'Product update',
|
|
159
|
+
description: 'Hero image, feature blocks, one CTA.',
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
id: 'newsletter',
|
|
163
|
+
name: 'Newsletter',
|
|
164
|
+
description: 'Multi-story digest with section dividers.',
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
id: 'plain-text',
|
|
168
|
+
name: 'Plain text',
|
|
169
|
+
description: 'No chrome — reads like a personal email.',
|
|
170
|
+
},
|
|
171
|
+
];
|
|
172
|
+
|
|
173
|
+
export const TIMEZONES = [
|
|
174
|
+
{ label: 'Rome (CET)', value: 'Europe/Rome' },
|
|
175
|
+
{ label: 'London (GMT)', value: 'Europe/London' },
|
|
176
|
+
{ label: 'New York (ET)', value: 'America/New_York' },
|
|
177
|
+
{ label: 'Chicago (CT)', value: 'America/Chicago' },
|
|
178
|
+
{ label: 'Los Angeles (PT)', value: 'America/Los_Angeles' },
|
|
179
|
+
{ label: 'Tokyo (JST)', value: 'Asia/Tokyo' },
|
|
180
|
+
{ label: 'Sydney (AEST)', value: 'Australia/Sydney' },
|
|
181
|
+
];
|
|
182
|
+
|
|
183
|
+
export const COUNTRIES = [
|
|
184
|
+
{ label: 'Italy', value: 'IT' },
|
|
185
|
+
{ label: 'France', value: 'FR' },
|
|
186
|
+
{ label: 'Germany', value: 'DE' },
|
|
187
|
+
{ label: 'United Kingdom', value: 'GB' },
|
|
188
|
+
{ label: 'United States', value: 'US' },
|
|
189
|
+
{ label: 'Canada', value: 'CA' },
|
|
190
|
+
];
|
|
191
|
+
|
|
192
|
+
/** Everything the four steps edit. Persisted verbatim by saveDraft/submit. */
|
|
193
|
+
export interface CampaignDraft {
|
|
194
|
+
// Audience
|
|
195
|
+
segmentId: number | null;
|
|
196
|
+
/** Empty array = every country in the segment. */
|
|
197
|
+
countries: string[];
|
|
198
|
+
/** Skip contacts already emailed in the last 7 days. */
|
|
199
|
+
excludeRecent: boolean;
|
|
200
|
+
// Content
|
|
201
|
+
subject: string;
|
|
202
|
+
preheader: string;
|
|
203
|
+
body: string;
|
|
204
|
+
templateId: string | null;
|
|
205
|
+
// Schedule
|
|
206
|
+
sendMode: SendMode;
|
|
207
|
+
/** ISO string from BbDatePickerInput; only meaningful when scheduled. */
|
|
208
|
+
sendAt: string | null;
|
|
209
|
+
timezone: string;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export interface StepIssue {
|
|
213
|
+
/** Draft field the issue belongs to — leaves match on it for inline errors. */
|
|
214
|
+
field: string;
|
|
215
|
+
message: string;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export interface CampaignWizardContext {
|
|
219
|
+
// -- Coopted contexts ------------------------------------------------------
|
|
220
|
+
shell: PageShellContext<PeekItem>;
|
|
221
|
+
// -- Wizard state ----------------------------------------------------------
|
|
222
|
+
loading: Ref<boolean>;
|
|
223
|
+
campaign: Ref<Campaign | null>;
|
|
224
|
+
draft: CampaignDraft;
|
|
225
|
+
step: Ref<StepKey>;
|
|
226
|
+
dirty: ComputedRef<boolean>;
|
|
227
|
+
lastSavedAt: Ref<string | null>;
|
|
228
|
+
submitting: Ref<boolean>;
|
|
229
|
+
// -- Validation ------------------------------------------------------------
|
|
230
|
+
issues: ComputedRef<Record<StepKey, StepIssue[]>>;
|
|
231
|
+
/** Steps whose Next was blocked at least once — only they show errors. */
|
|
232
|
+
attempted: Record<StepKey, boolean>;
|
|
233
|
+
stepStatus: (key: StepKey) => StepStatus;
|
|
234
|
+
fieldErrors: (step: StepKey, field: string) => string[];
|
|
235
|
+
// -- Audience intel --------------------------------------------------------
|
|
236
|
+
selectedSegment: Ref<Segment | null>;
|
|
237
|
+
estimatedRecipients: Ref<number | null>;
|
|
238
|
+
// -- Peek grammar ----------------------------------------------------------
|
|
239
|
+
peekSegment: () => void;
|
|
240
|
+
// -- Actions (complete: API + state + toast) --------------------------------
|
|
241
|
+
goToStep: (key: StepKey) => void;
|
|
242
|
+
goBack: () => void;
|
|
243
|
+
goNext: () => void;
|
|
244
|
+
saveDraft: () => Promise<void>;
|
|
245
|
+
sendTest: () => Promise<void>;
|
|
246
|
+
submit: () => Promise<void>;
|
|
247
|
+
discardDraft: () => Promise<void>;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const key: InjectionKey<CampaignWizardContext> = Symbol('campaign-wizard');
|
|
251
|
+
|
|
252
|
+
/** Which step owns each server-validated field — used to route a failed
|
|
253
|
+
* save's 422 errors onto the right step, merged into `issues` below. */
|
|
254
|
+
const FIELDS_BY_STEP: Record<StepKey, string[]> = {
|
|
255
|
+
audience: ['segmentId'],
|
|
256
|
+
content: ['subject', 'body', 'templateId'],
|
|
257
|
+
schedule: ['sendAt', 'timezone'],
|
|
258
|
+
review: [],
|
|
259
|
+
};
|
|
260
|
+
const stepForField = (field: string): StepKey | undefined =>
|
|
261
|
+
(Object.keys(FIELDS_BY_STEP) as StepKey[]).find((stepKey) =>
|
|
262
|
+
FIELDS_BY_STEP[stepKey].includes(field)
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
export function provideCampaignWizard(): CampaignWizardContext {
|
|
266
|
+
const { toast } = useToast();
|
|
267
|
+
const { confirm } = useConfirm();
|
|
268
|
+
const route = useRoute();
|
|
269
|
+
|
|
270
|
+
// -- Coopt the specific contexts: once, here, for the whole page. ----------
|
|
271
|
+
const shell = usePageShellContext<PeekItem>();
|
|
272
|
+
|
|
273
|
+
// -- Wizard state -----------------------------------------------------------
|
|
274
|
+
const campaignId = Number(route.params.campaignId);
|
|
275
|
+
|
|
276
|
+
const draft = reactive<CampaignDraft>({
|
|
277
|
+
segmentId: null,
|
|
278
|
+
countries: [],
|
|
279
|
+
excludeRecent: true,
|
|
280
|
+
subject: '',
|
|
281
|
+
preheader: '',
|
|
282
|
+
body: '',
|
|
283
|
+
templateId: null,
|
|
284
|
+
sendMode: 'now',
|
|
285
|
+
sendAt: null,
|
|
286
|
+
timezone: 'Europe/Rome',
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// The draft lives on the server (the list page created it before
|
|
290
|
+
// navigating here, Mailchimp-style); useAsyncData both fetches it and
|
|
291
|
+
// gives the page a reactive `pending` flag.
|
|
292
|
+
const { data } = useAsyncData('campaign', () =>
|
|
293
|
+
$fetch<{ campaign: Campaign; draft: CampaignDraft }>(
|
|
294
|
+
`/api/campaigns/${campaignId}`
|
|
295
|
+
)
|
|
296
|
+
);
|
|
297
|
+
const campaign = computed(() => data.value?.campaign ?? null);
|
|
298
|
+
const loading = computed(() => data.value === null);
|
|
299
|
+
|
|
300
|
+
// -- Server-side (422) errors, merged into `issues` below so the stepper,
|
|
301
|
+
// the alert summary and the inline errors all read one source. -------------
|
|
302
|
+
const serverErrors = reactive<Record<string, string[]>>({});
|
|
303
|
+
|
|
304
|
+
// -- Validation ---------------------------------------------------------------
|
|
305
|
+
// Live on every keystroke; SURFACED only per `attempted` (set when a Next,
|
|
306
|
+
// submit or save is blocked), so untouched steps never open covered in red.
|
|
307
|
+
const issues = computed<Record<StepKey, StepIssue[]>>(() => {
|
|
308
|
+
const audience: StepIssue[] = [];
|
|
309
|
+
if (draft.segmentId === null)
|
|
310
|
+
audience.push({
|
|
311
|
+
field: 'segmentId',
|
|
312
|
+
message: 'Choose the segment this campaign goes to.',
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
const content: StepIssue[] = [];
|
|
316
|
+
if (!draft.subject.trim())
|
|
317
|
+
content.push({ field: 'subject', message: 'Write a subject line.' });
|
|
318
|
+
else if (draft.subject.length > 150)
|
|
319
|
+
content.push({
|
|
320
|
+
field: 'subject',
|
|
321
|
+
message: 'Keep the subject under 150 characters.',
|
|
322
|
+
});
|
|
323
|
+
if (!draft.body.trim())
|
|
324
|
+
content.push({
|
|
325
|
+
field: 'body',
|
|
326
|
+
message: 'The email body cannot be empty.',
|
|
327
|
+
});
|
|
328
|
+
if (draft.templateId === null)
|
|
329
|
+
content.push({ field: 'templateId', message: 'Pick a template.' });
|
|
330
|
+
|
|
331
|
+
const schedule: StepIssue[] = [];
|
|
332
|
+
if (draft.sendMode === 'scheduled') {
|
|
333
|
+
if (!draft.sendAt)
|
|
334
|
+
schedule.push({ field: 'sendAt', message: 'Pick a send date.' });
|
|
335
|
+
if (!draft.timezone)
|
|
336
|
+
schedule.push({ field: 'timezone', message: 'Pick a timezone.' });
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Merge server (422) errors onto the step that owns each field, so a
|
|
340
|
+
// failed save surfaces exactly like a blocked Next.
|
|
341
|
+
const withServerErrors = (
|
|
342
|
+
stepKey: StepKey,
|
|
343
|
+
list: StepIssue[]
|
|
344
|
+
): StepIssue[] => [
|
|
345
|
+
...list,
|
|
346
|
+
...FIELDS_BY_STEP[stepKey]
|
|
347
|
+
.filter((field) => serverErrors[field]?.length)
|
|
348
|
+
.flatMap((field) =>
|
|
349
|
+
serverErrors[field].map((message) => ({ field, message }))
|
|
350
|
+
),
|
|
351
|
+
];
|
|
352
|
+
|
|
353
|
+
return {
|
|
354
|
+
audience: withServerErrors('audience', audience),
|
|
355
|
+
content: withServerErrors('content', content),
|
|
356
|
+
schedule: withServerErrors('schedule', schedule),
|
|
357
|
+
review: [],
|
|
358
|
+
};
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
const attempted = reactive<Record<StepKey, boolean>>({
|
|
362
|
+
audience: false,
|
|
363
|
+
content: false,
|
|
364
|
+
schedule: false,
|
|
365
|
+
review: false,
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
const fieldErrors = (stepKey: StepKey, field: string): string[] =>
|
|
369
|
+
attempted[stepKey]
|
|
370
|
+
? issues.value[stepKey]
|
|
371
|
+
.filter((issue) => issue.field === field)
|
|
372
|
+
.map((issue) => issue.message)
|
|
373
|
+
: [];
|
|
374
|
+
|
|
375
|
+
// -- Step machine ---------------------------------------------------------------
|
|
376
|
+
const step = ref<StepKey>('audience');
|
|
377
|
+
|
|
378
|
+
const indexOf = (stepKey: StepKey) =>
|
|
379
|
+
STEPS.findIndex((item) => item.key === stepKey);
|
|
380
|
+
|
|
381
|
+
// The single gating predicate: a step is reachable when every step
|
|
382
|
+
// before it validates. Next, the stepper, and submit all derive from it.
|
|
383
|
+
const unlocked = (stepKey: StepKey) =>
|
|
384
|
+
STEPS.slice(0, indexOf(stepKey)).every(
|
|
385
|
+
(item) => issues.value[item.key].length === 0
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
const stepStatus = (stepKey: StepKey): StepStatus => {
|
|
389
|
+
if (stepKey === step.value) return 'current';
|
|
390
|
+
// Only steps already passed count as done — unlocked future steps stay
|
|
391
|
+
// numbered (upcoming), not checkmarked.
|
|
392
|
+
if (indexOf(stepKey) < indexOf(step.value)) return 'done';
|
|
393
|
+
return unlocked(stepKey) ? 'upcoming' : 'locked';
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
const firstIncompleteStep = (): StepKey =>
|
|
397
|
+
STEPS.find((item) => issues.value[item.key].length > 0)?.key ?? 'review';
|
|
398
|
+
|
|
399
|
+
const goToStep = (stepKey: StepKey) => {
|
|
400
|
+
if (stepKey !== step.value && stepStatus(stepKey) === 'locked') return;
|
|
401
|
+
step.value = stepKey;
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
const goBack = () => {
|
|
405
|
+
const previous = STEPS[indexOf(step.value) - 1];
|
|
406
|
+
if (previous) step.value = previous.key;
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
const goNext = () => {
|
|
410
|
+
const current = step.value;
|
|
411
|
+
if (issues.value[current].length > 0) {
|
|
412
|
+
attempted[current] = true; // raises the alert + inline errors
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
attempted[current] = false;
|
|
416
|
+
const next = STEPS[indexOf(current) + 1];
|
|
417
|
+
if (next) step.value = next.key;
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
// Mirror the step into the URL. `navigateTo` + `replace: true` is Nuxt's
|
|
421
|
+
// router.replace — no full navigation, no extra history entry.
|
|
422
|
+
watch(step, (next) => {
|
|
423
|
+
void navigateTo(
|
|
424
|
+
{ query: { ...route.query, step: String(indexOf(next) + 1) } },
|
|
425
|
+
{ replace: true }
|
|
426
|
+
);
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
// -- Audience intel ---------------------------------------------------------------
|
|
430
|
+
// The select's v-model holds only the id; Review and the summary box need
|
|
431
|
+
// the record. Same resolve-by-id pattern as the flagship's customer pill.
|
|
432
|
+
const selectedSegment = ref<Segment | null>(null);
|
|
433
|
+
watchEffect(async () => {
|
|
434
|
+
selectedSegment.value =
|
|
435
|
+
draft.segmentId === null
|
|
436
|
+
? null
|
|
437
|
+
: await $fetch<Segment>(`/api/segments/${draft.segmentId}`);
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
const estimatedRecipients = ref<number | null>(null);
|
|
441
|
+
watchEffect(async () => {
|
|
442
|
+
if (draft.segmentId === null) {
|
|
443
|
+
estimatedRecipients.value = null;
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
estimatedRecipients.value = await $fetch<number>(
|
|
447
|
+
`/api/segments/${draft.segmentId}/estimate`,
|
|
448
|
+
{
|
|
449
|
+
query: {
|
|
450
|
+
countries: draft.countries,
|
|
451
|
+
excludeRecent: draft.excludeRecent,
|
|
452
|
+
},
|
|
453
|
+
}
|
|
454
|
+
);
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
// -- Peek grammar ----------------------------------------------------------------
|
|
458
|
+
const peekSegment = () => {
|
|
459
|
+
if (draft.segmentId !== null)
|
|
460
|
+
shell.push({ type: 'segment', id: draft.segmentId }, true);
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
// -- Persistence + dirty guard ------------------------------------------------------
|
|
464
|
+
const snapshot = ref('');
|
|
465
|
+
const lastSavedAt = ref<string | null>(null);
|
|
466
|
+
const dirty = computed(
|
|
467
|
+
() => !loading.value && JSON.stringify(draft) !== snapshot.value
|
|
468
|
+
);
|
|
469
|
+
|
|
470
|
+
// Seed the draft once the fetch lands, and clamp ?step so a stale or
|
|
471
|
+
// forged link can't open a locked step.
|
|
472
|
+
watch(
|
|
473
|
+
data,
|
|
474
|
+
(response) => {
|
|
475
|
+
if (!response) return;
|
|
476
|
+
Object.assign(draft, response.draft);
|
|
477
|
+
snapshot.value = JSON.stringify(draft);
|
|
478
|
+
const requested = STEPS[Number(route.query.step) - 1]?.key ?? 'audience';
|
|
479
|
+
step.value =
|
|
480
|
+
stepStatus(requested) === 'locked' ? firstIncompleteStep() : requested;
|
|
481
|
+
},
|
|
482
|
+
{ immediate: true }
|
|
483
|
+
);
|
|
484
|
+
|
|
485
|
+
onBeforeRouteLeave(async () => {
|
|
486
|
+
if (!dirty.value) return true;
|
|
487
|
+
return confirm({
|
|
488
|
+
variant: 'destructive',
|
|
489
|
+
title: 'Leave campaign setup?',
|
|
490
|
+
text: 'You have unsaved changes. Leave and lose them, or stay and save the draft.',
|
|
491
|
+
yes: 'Leave',
|
|
492
|
+
no: 'Keep editing',
|
|
493
|
+
});
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
// -- Actions --------------------------------------------------------------------
|
|
497
|
+
// `$fetch` throws a typed `FetchError`; 422 means field errors — merge
|
|
498
|
+
// them into `serverErrors` (read by `issues` above) and jump to the
|
|
499
|
+
// first broken step, exactly like a blocked Next.
|
|
500
|
+
const applyServerErrors = (error: unknown): void => {
|
|
501
|
+
if (!(error instanceof FetchError) || error.statusCode !== 422) throw error;
|
|
502
|
+
Object.keys(serverErrors).forEach((field) => delete serverErrors[field]);
|
|
503
|
+
const incoming = (error.data?.errors as Record<string, string[]>) ?? {};
|
|
504
|
+
Object.assign(serverErrors, incoming);
|
|
505
|
+
const brokenStep = Object.keys(incoming)
|
|
506
|
+
.map(stepForField)
|
|
507
|
+
.find((found): found is StepKey => Boolean(found));
|
|
508
|
+
if (brokenStep) {
|
|
509
|
+
attempted[brokenStep] = true;
|
|
510
|
+
step.value = brokenStep;
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
const saveDraft = async () => {
|
|
515
|
+
Object.keys(serverErrors).forEach((field) => delete serverErrors[field]);
|
|
516
|
+
try {
|
|
517
|
+
await $fetch(`/api/campaigns/${campaignId}`, {
|
|
518
|
+
method: 'PATCH',
|
|
519
|
+
body: { ...draft },
|
|
520
|
+
});
|
|
521
|
+
snapshot.value = JSON.stringify(draft);
|
|
522
|
+
lastSavedAt.value = new Date().toISOString();
|
|
523
|
+
toast({ title: 'Draft saved.', variant: 'success' });
|
|
524
|
+
} catch (error) {
|
|
525
|
+
applyServerErrors(error);
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
const sendTest = async () => {
|
|
530
|
+
if (issues.value.content.length > 0) {
|
|
531
|
+
attempted.content = true; // same surfacing as a blocked Next
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
await $fetch(`/api/campaigns/${campaignId}/send-test`, {
|
|
535
|
+
method: 'POST',
|
|
536
|
+
body: { ...draft },
|
|
537
|
+
});
|
|
538
|
+
toast({ title: 'Test email sent to your address.', variant: 'success' });
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
const submitting = ref(false);
|
|
542
|
+
const submit = async () => {
|
|
543
|
+
// Whole-wizard validation: land the user on the first broken step.
|
|
544
|
+
const incomplete = STEPS.find((item) => issues.value[item.key].length > 0);
|
|
545
|
+
if (incomplete) {
|
|
546
|
+
attempted[incomplete.key] = true;
|
|
547
|
+
step.value = incomplete.key;
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
submitting.value = true;
|
|
551
|
+
Object.keys(serverErrors).forEach((field) => delete serverErrors[field]);
|
|
552
|
+
try {
|
|
553
|
+
await $fetch(`/api/campaigns/${campaignId}`, {
|
|
554
|
+
method: 'PATCH',
|
|
555
|
+
body: { ...draft },
|
|
556
|
+
});
|
|
557
|
+
await $fetch(`/api/campaigns/${campaignId}/send`, { method: 'POST' });
|
|
558
|
+
snapshot.value = JSON.stringify(draft); // the guard has nothing left to protect
|
|
559
|
+
toast({
|
|
560
|
+
title:
|
|
561
|
+
draft.sendMode === 'now'
|
|
562
|
+
? 'Campaign is on its way.'
|
|
563
|
+
: 'Campaign scheduled.',
|
|
564
|
+
variant: 'success',
|
|
565
|
+
});
|
|
566
|
+
await navigateTo(`/campaigns/${campaignId}`);
|
|
567
|
+
} catch (error) {
|
|
568
|
+
applyServerErrors(error);
|
|
569
|
+
} finally {
|
|
570
|
+
submitting.value = false;
|
|
571
|
+
}
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
const discardDraft = async () => {
|
|
575
|
+
const ok = await confirm({
|
|
576
|
+
variant: 'destructive',
|
|
577
|
+
title: 'Discard draft',
|
|
578
|
+
text: `Delete "${campaign.value?.name ?? 'this campaign'}" and everything entered so far? This cannot be undone.`,
|
|
579
|
+
yes: 'Discard',
|
|
580
|
+
no: 'Keep draft',
|
|
581
|
+
});
|
|
582
|
+
if (!ok) return;
|
|
583
|
+
snapshot.value = JSON.stringify(draft); // don't double-prompt via the leave guard
|
|
584
|
+
await $fetch(`/api/campaigns/${campaignId}`, { method: 'DELETE' });
|
|
585
|
+
toast({ title: 'Draft discarded.', variant: 'success' });
|
|
586
|
+
await navigateTo('/campaigns');
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
const ctx: CampaignWizardContext = {
|
|
590
|
+
shell,
|
|
591
|
+
loading,
|
|
592
|
+
campaign,
|
|
593
|
+
draft,
|
|
594
|
+
step,
|
|
595
|
+
dirty,
|
|
596
|
+
lastSavedAt,
|
|
597
|
+
submitting,
|
|
598
|
+
issues,
|
|
599
|
+
attempted,
|
|
600
|
+
stepStatus,
|
|
601
|
+
fieldErrors,
|
|
602
|
+
selectedSegment,
|
|
603
|
+
estimatedRecipients,
|
|
604
|
+
peekSegment,
|
|
605
|
+
goToStep,
|
|
606
|
+
goBack,
|
|
607
|
+
goNext,
|
|
608
|
+
saveDraft,
|
|
609
|
+
sendTest,
|
|
610
|
+
submit,
|
|
611
|
+
discardDraft,
|
|
612
|
+
};
|
|
613
|
+
provide(key, ctx);
|
|
614
|
+
return ctx;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
export function useCampaignWizard(): CampaignWizardContext {
|
|
618
|
+
const ctx = inject(key);
|
|
619
|
+
if (!ctx)
|
|
620
|
+
throw new Error('useCampaignWizard() called outside CampaignWizard');
|
|
621
|
+
return ctx;
|
|
622
|
+
}
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
### Index.vue — the assembly
|
|
626
|
+
|
|
627
|
+
```vue
|
|
628
|
+
<!-- CampaignWizard/Index.vue -->
|
|
629
|
+
<template>
|
|
630
|
+
<PageShell>
|
|
631
|
+
<!-- Topbar: breadcrumbs left; save state + the destructive escape right.
|
|
632
|
+
Wizard archetype: no page search, no filter rail, no tabs. -->
|
|
633
|
+
<template #topbar>
|
|
634
|
+
<div class="flex items-center justify-between gap-4 border-b px-4 py-2">
|
|
635
|
+
<!-- Breadcrumbs take the row's slack on a stable basis (min-w-0 flex-auto);
|
|
636
|
+
without it their overflow recompute fights the flex layout every frame. -->
|
|
637
|
+
<BbBreadcrumbs class="min-w-0 flex-auto" :items="crumbs" />
|
|
638
|
+
<div class="flex shrink-0 items-center gap-3">
|
|
639
|
+
<span v-if="savedLabel" class="text-xs text-gray-500">{{
|
|
640
|
+
savedLabel
|
|
641
|
+
}}</span>
|
|
642
|
+
<BbButton
|
|
643
|
+
prepend:icon="lucide:trash-2"
|
|
644
|
+
variant="destructive"
|
|
645
|
+
@click="ctx.discardDraft"
|
|
646
|
+
>
|
|
647
|
+
Discard draft
|
|
648
|
+
</BbButton>
|
|
649
|
+
</div>
|
|
650
|
+
</div>
|
|
651
|
+
</template>
|
|
652
|
+
|
|
653
|
+
<div class="mx-auto flex w-full max-w-3xl flex-col gap-5 p-4">
|
|
654
|
+
<header class="flex flex-col gap-0.5">
|
|
655
|
+
<h1 class="text-lg font-semibold">
|
|
656
|
+
{{ ctx.campaign.value?.name ?? 'New campaign' }}
|
|
657
|
+
</h1>
|
|
658
|
+
<p class="text-sm text-gray-500">
|
|
659
|
+
Set up the audience, content and schedule, then review and send.
|
|
660
|
+
</p>
|
|
661
|
+
</header>
|
|
662
|
+
|
|
663
|
+
<StepperHeader />
|
|
664
|
+
|
|
665
|
+
<!-- One wrapper around the alert AND the step outlet: the step swap
|
|
666
|
+
and the validation summary appearing/shrinking both glide. -->
|
|
667
|
+
<!-- Enable height motion only after mount so first paint does not
|
|
668
|
+
animate in; later step swaps still glide. -->
|
|
669
|
+
<BbSmoothHeight :disabled="!stepHeightReady">
|
|
670
|
+
<div class="flex flex-col gap-4">
|
|
671
|
+
<BbAlert
|
|
672
|
+
v-if="summaryIssues.length"
|
|
673
|
+
hide-close
|
|
674
|
+
icon="lucide:circle-x"
|
|
675
|
+
:title="`Fix ${summaryIssues.length} ${summaryIssues.length === 1 ? 'issue' : 'issues'} to continue`"
|
|
676
|
+
variant="destructive"
|
|
677
|
+
>
|
|
678
|
+
<template #text>
|
|
679
|
+
<ul class="list-disc pl-4">
|
|
680
|
+
<li v-for="issue in summaryIssues" :key="issue.field">
|
|
681
|
+
{{ issue.message }}
|
|
682
|
+
</li>
|
|
683
|
+
</ul>
|
|
684
|
+
</template>
|
|
685
|
+
</BbAlert>
|
|
686
|
+
|
|
687
|
+
<div v-if="ctx.loading.value" class="flex justify-center p-10">
|
|
688
|
+
<BbSpinner />
|
|
689
|
+
</div>
|
|
690
|
+
<AudienceStep v-else-if="ctx.step.value === 'audience'" />
|
|
691
|
+
<ContentStep v-else-if="ctx.step.value === 'content'" />
|
|
692
|
+
<ScheduleStep v-else-if="ctx.step.value === 'schedule'" />
|
|
693
|
+
<ReviewStep v-else />
|
|
694
|
+
</div>
|
|
695
|
+
</BbSmoothHeight>
|
|
696
|
+
|
|
697
|
+
<WizardFooter />
|
|
698
|
+
</div>
|
|
699
|
+
|
|
700
|
+
<!-- Clicking the segment's NAME peeks it — Audience step and Review. -->
|
|
701
|
+
<template #sidebar>
|
|
702
|
+
<SegmentPeek
|
|
703
|
+
v-if="ctx.shell.current.value?.type === 'segment'"
|
|
704
|
+
:segment-id="ctx.shell.current.value.id"
|
|
705
|
+
/>
|
|
706
|
+
</template>
|
|
707
|
+
</PageShell>
|
|
708
|
+
</template>
|
|
709
|
+
|
|
710
|
+
<script setup lang="ts">
|
|
711
|
+
import { computed, onMounted, ref } from 'vue';
|
|
712
|
+
import {
|
|
713
|
+
BbAlert,
|
|
714
|
+
BbBreadcrumbs,
|
|
715
|
+
BbButton,
|
|
716
|
+
BbSmoothHeight,
|
|
717
|
+
BbSpinner,
|
|
718
|
+
} from 'bitboss-ui';
|
|
719
|
+
import PageShell from '@/components/layout/PageShell.vue';
|
|
720
|
+
import { provideCampaignWizard } from './context';
|
|
721
|
+
import AudienceStep from './AudienceStep.vue';
|
|
722
|
+
import ContentStep from './ContentStep.vue';
|
|
723
|
+
import ReviewStep from './ReviewStep.vue';
|
|
724
|
+
import ScheduleStep from './ScheduleStep.vue';
|
|
725
|
+
import SegmentPeek from './SegmentPeek.vue';
|
|
726
|
+
import StepperHeader from './StepperHeader.vue';
|
|
727
|
+
import WizardFooter from './WizardFooter.vue';
|
|
728
|
+
|
|
729
|
+
const ctx = provideCampaignWizard();
|
|
730
|
+
|
|
731
|
+
/** Keep SmoothHeight off while the first paint settles (initial load swaps
|
|
732
|
+
* spinner → step content); enable after 300ms for step-to-step glides. */
|
|
733
|
+
const stepHeightReady = ref(false);
|
|
734
|
+
onMounted(() => {
|
|
735
|
+
setTimeout(() => {
|
|
736
|
+
stepHeightReady.value = true;
|
|
737
|
+
}, 300);
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
const crumbs = computed(() => [
|
|
741
|
+
{ key: 'dashboard', text: 'Dashboard', to: '/dashboard' },
|
|
742
|
+
{ key: 'campaigns', text: 'Campaigns', to: '/campaigns' },
|
|
743
|
+
{ key: 'setup', text: ctx.campaign.value?.name ?? 'New campaign' },
|
|
744
|
+
]);
|
|
745
|
+
|
|
746
|
+
// The summary shows only the CURRENT step's issues, and only once the user
|
|
747
|
+
// tried to leave it — it shrinks live as each problem is fixed.
|
|
748
|
+
const summaryIssues = computed(() =>
|
|
749
|
+
ctx.attempted[ctx.step.value] ? ctx.issues.value[ctx.step.value] : []
|
|
750
|
+
);
|
|
751
|
+
|
|
752
|
+
const savedLabel = computed(() => {
|
|
753
|
+
if (ctx.dirty.value) return 'Unsaved changes';
|
|
754
|
+
if (!ctx.lastSavedAt.value) return null;
|
|
755
|
+
const time = new Date(ctx.lastSavedAt.value).toLocaleTimeString('en-US', {
|
|
756
|
+
hour: 'numeric',
|
|
757
|
+
minute: '2-digit',
|
|
758
|
+
});
|
|
759
|
+
return `Draft saved at ${time}`;
|
|
760
|
+
});
|
|
761
|
+
</script>
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
### StepperHeader.vue
|
|
765
|
+
|
|
766
|
+
```vue
|
|
767
|
+
<!-- CampaignWizard/StepperHeader.vue -->
|
|
768
|
+
<template>
|
|
769
|
+
<nav aria-label="Campaign setup steps" class="flex items-center gap-1">
|
|
770
|
+
<template v-for="(stepMeta, index) in STEPS" :key="stepMeta.key">
|
|
771
|
+
<!-- Connector line between steps. -->
|
|
772
|
+
<div
|
|
773
|
+
v-if="index > 0"
|
|
774
|
+
aria-hidden="true"
|
|
775
|
+
class="h-px min-w-3 flex-1 bg-[var(--bb-border)]"
|
|
776
|
+
/>
|
|
777
|
+
|
|
778
|
+
<!-- The primitive gives element semantics + disabled handling; the
|
|
779
|
+
look is entirely ours. Done and upcoming steps jump, locked
|
|
780
|
+
steps are inert. -->
|
|
781
|
+
<BbBaseButton
|
|
782
|
+
:aria-current="status(stepMeta.key) === 'current' ? 'step' : undefined"
|
|
783
|
+
class="flex items-center gap-2 rounded-[var(--bb-radius)] px-2 py-1.5 text-sm focus-visible:ring-2 focus-visible:ring-[var(--bb-primary)]"
|
|
784
|
+
:class="{
|
|
785
|
+
'bg-[var(--bb-hover)] font-medium':
|
|
786
|
+
status(stepMeta.key) === 'current',
|
|
787
|
+
'hover:bg-[var(--bb-hover)]': ['done', 'upcoming'].includes(
|
|
788
|
+
status(stepMeta.key)
|
|
789
|
+
),
|
|
790
|
+
'text-gray-400': status(stepMeta.key) === 'locked',
|
|
791
|
+
}"
|
|
792
|
+
:disabled="status(stepMeta.key) === 'locked'"
|
|
793
|
+
@click="() => ctx.goToStep(stepMeta.key)"
|
|
794
|
+
>
|
|
795
|
+
<span
|
|
796
|
+
class="flex size-5 shrink-0 items-center justify-center rounded-full border text-xs"
|
|
797
|
+
:class="{
|
|
798
|
+
'border-transparent bg-[var(--bb-primary)] text-[var(--bb-primary-fg)]':
|
|
799
|
+
status(stepMeta.key) === 'done',
|
|
800
|
+
'border-[var(--bb-primary)] text-[color:var(--bb-primary)]':
|
|
801
|
+
status(stepMeta.key) === 'current',
|
|
802
|
+
'border-[var(--bb-border)]': status(stepMeta.key) === 'locked',
|
|
803
|
+
}"
|
|
804
|
+
>
|
|
805
|
+
<BbIcon
|
|
806
|
+
v-if="status(stepMeta.key) === 'done'"
|
|
807
|
+
icon="lucide:check"
|
|
808
|
+
size="12"
|
|
809
|
+
/>
|
|
810
|
+
<template v-else>{{ index + 1 }}</template>
|
|
811
|
+
</span>
|
|
812
|
+
{{ stepMeta.label }}
|
|
813
|
+
</BbBaseButton>
|
|
814
|
+
</template>
|
|
815
|
+
</nav>
|
|
816
|
+
</template>
|
|
817
|
+
|
|
818
|
+
<script setup lang="ts">
|
|
819
|
+
import { BbBaseButton, BbIcon } from 'bitboss-ui';
|
|
820
|
+
import { STEPS, useCampaignWizard, type StepKey } from './context';
|
|
821
|
+
|
|
822
|
+
const ctx = useCampaignWizard();
|
|
823
|
+
|
|
824
|
+
const status = (key: StepKey) => ctx.stepStatus(key);
|
|
825
|
+
</script>
|
|
826
|
+
```
|
|
827
|
+
|
|
828
|
+
### AudienceStep.vue
|
|
829
|
+
|
|
830
|
+
```vue
|
|
831
|
+
<!-- CampaignWizard/AudienceStep.vue -->
|
|
832
|
+
<template>
|
|
833
|
+
<section class="flex flex-col gap-4">
|
|
834
|
+
<div class="flex flex-col gap-0.5">
|
|
835
|
+
<h2 class="text-base font-semibold">Who gets this email?</h2>
|
|
836
|
+
<p class="text-sm text-gray-500">
|
|
837
|
+
Pick a segment, then narrow it with delivery filters.
|
|
838
|
+
</p>
|
|
839
|
+
</div>
|
|
840
|
+
|
|
841
|
+
<!-- Query-aware items function: the select searches the API as you type. -->
|
|
842
|
+
<BbSelect
|
|
843
|
+
v-model="ctx.draft.segmentId"
|
|
844
|
+
:errors="segmentErrors"
|
|
845
|
+
item-text="name"
|
|
846
|
+
item-value="id"
|
|
847
|
+
:items="searchSegments"
|
|
848
|
+
label="Segment"
|
|
849
|
+
label-mode="outside"
|
|
850
|
+
placeholder="Search segments…"
|
|
851
|
+
required
|
|
852
|
+
/>
|
|
853
|
+
|
|
854
|
+
<!-- Resolved segment + live estimate: the context owns both fetches. -->
|
|
855
|
+
<div
|
|
856
|
+
v-if="ctx.selectedSegment.value"
|
|
857
|
+
class="flex flex-col gap-1 rounded-[var(--bb-radius)] border p-3 text-sm"
|
|
858
|
+
>
|
|
859
|
+
<div class="flex items-center justify-between gap-2">
|
|
860
|
+
<BbButton size="sm" variant="link" @click="ctx.peekSegment">
|
|
861
|
+
{{ ctx.selectedSegment.value.name }}
|
|
862
|
+
</BbButton>
|
|
863
|
+
<span class="text-xs text-gray-500">
|
|
864
|
+
Last synced {{ shortDate(ctx.selectedSegment.value.lastSyncedAt) }}
|
|
865
|
+
</span>
|
|
866
|
+
</div>
|
|
867
|
+
<p class="text-gray-500">
|
|
868
|
+
{{ number.format(ctx.selectedSegment.value.contactCount) }} contacts in
|
|
869
|
+
the segment ·
|
|
870
|
+
<strong class="text-[var(--bb-text)]">
|
|
871
|
+
{{
|
|
872
|
+
ctx.estimatedRecipients.value === null
|
|
873
|
+
? '…'
|
|
874
|
+
: number.format(ctx.estimatedRecipients.value)
|
|
875
|
+
}}
|
|
876
|
+
</strong>
|
|
877
|
+
estimated recipients after filters
|
|
878
|
+
</p>
|
|
879
|
+
</div>
|
|
880
|
+
|
|
881
|
+
<BbSelect
|
|
882
|
+
v-model="ctx.draft.countries"
|
|
883
|
+
:items="COUNTRIES"
|
|
884
|
+
item-text="label"
|
|
885
|
+
item-value="value"
|
|
886
|
+
label="Limit to countries"
|
|
887
|
+
label-mode="outside"
|
|
888
|
+
multiple
|
|
889
|
+
placeholder="All countries"
|
|
890
|
+
/>
|
|
891
|
+
|
|
892
|
+
<BbSwitch
|
|
893
|
+
v-model="ctx.draft.excludeRecent"
|
|
894
|
+
description="Avoids fatigue: anyone who already received a campaign this week is skipped."
|
|
895
|
+
label="Skip contacts emailed in the last 7 days"
|
|
896
|
+
/>
|
|
897
|
+
</section>
|
|
898
|
+
</template>
|
|
899
|
+
|
|
900
|
+
<script setup lang="ts">
|
|
901
|
+
import { computed } from 'vue';
|
|
902
|
+
import { BbButton, BbSelect, BbSwitch } from 'bitboss-ui';
|
|
903
|
+
import { COUNTRIES, useCampaignWizard } from './context';
|
|
904
|
+
import type { Segment } from '@/types';
|
|
905
|
+
|
|
906
|
+
const ctx = useCampaignWizard();
|
|
907
|
+
|
|
908
|
+
// A small JSON endpoint — query-aware, so it stays a live search instead of
|
|
909
|
+
// a static lazy list.
|
|
910
|
+
const searchSegments = (query: string): Promise<Segment[]> =>
|
|
911
|
+
$fetch<Segment[]>('/api/segments', { query: { search: query } });
|
|
912
|
+
|
|
913
|
+
// Inline errors appear only after a blocked Next (attempted), per the context.
|
|
914
|
+
const segmentErrors = computed(() => ctx.fieldErrors('audience', 'segmentId'));
|
|
915
|
+
|
|
916
|
+
const number = new Intl.NumberFormat('en-US');
|
|
917
|
+
const shortDate = (value: string) =>
|
|
918
|
+
new Date(value).toLocaleDateString('en-US', {
|
|
919
|
+
month: 'short',
|
|
920
|
+
day: 'numeric',
|
|
921
|
+
});
|
|
922
|
+
</script>
|
|
923
|
+
```
|
|
924
|
+
|
|
925
|
+
### ContentStep.vue
|
|
926
|
+
|
|
927
|
+
```vue
|
|
928
|
+
<!-- CampaignWizard/ContentStep.vue -->
|
|
929
|
+
<template>
|
|
930
|
+
<section class="flex flex-col gap-4">
|
|
931
|
+
<div class="flex flex-col gap-0.5">
|
|
932
|
+
<h2 class="text-base font-semibold">What does it say?</h2>
|
|
933
|
+
<p class="text-sm text-gray-500">
|
|
934
|
+
Subject and preheader are what people see in the inbox list.
|
|
935
|
+
</p>
|
|
936
|
+
</div>
|
|
937
|
+
|
|
938
|
+
<BbTextInput
|
|
939
|
+
v-model="ctx.draft.subject"
|
|
940
|
+
:description="`${ctx.draft.subject.length}/150 characters`"
|
|
941
|
+
:errors="errorsFor('subject')"
|
|
942
|
+
label="Subject line"
|
|
943
|
+
label-mode="outside"
|
|
944
|
+
placeholder="The July roast drop is here — 20% off subscriptions"
|
|
945
|
+
required
|
|
946
|
+
/>
|
|
947
|
+
|
|
948
|
+
<BbTextInput
|
|
949
|
+
v-model="ctx.draft.preheader"
|
|
950
|
+
description="Optional. Shows after the subject in most inboxes — keep it under 90 characters."
|
|
951
|
+
label="Preheader"
|
|
952
|
+
label-mode="outside"
|
|
953
|
+
placeholder="Three new single origins, roasted this week."
|
|
954
|
+
/>
|
|
955
|
+
|
|
956
|
+
<!-- Fixed rows on purpose: an auto-growing textarea inside BbSmoothHeight
|
|
957
|
+
would re-animate the outlet on every line break while typing. -->
|
|
958
|
+
<BbTextarea
|
|
959
|
+
v-model="ctx.draft.body"
|
|
960
|
+
:errors="errorsFor('body')"
|
|
961
|
+
label="Body"
|
|
962
|
+
label-mode="outside"
|
|
963
|
+
placeholder="Write the email…"
|
|
964
|
+
required
|
|
965
|
+
:rows="10"
|
|
966
|
+
/>
|
|
967
|
+
|
|
968
|
+
<BbRadioGroup
|
|
969
|
+
v-model="ctx.draft.templateId"
|
|
970
|
+
:errors="errorsFor('templateId')"
|
|
971
|
+
item-text="name"
|
|
972
|
+
item-value="id"
|
|
973
|
+
:items="TEMPLATES"
|
|
974
|
+
legend="Template"
|
|
975
|
+
name="template"
|
|
976
|
+
required
|
|
977
|
+
>
|
|
978
|
+
<!-- Name + one-line description per option, from the same items. -->
|
|
979
|
+
<template #label="{ item, text }">
|
|
980
|
+
<span class="flex flex-col">
|
|
981
|
+
<span>{{ text }}</span>
|
|
982
|
+
<span class="text-xs text-gray-500">{{ item.description }}</span>
|
|
983
|
+
</span>
|
|
984
|
+
</template>
|
|
985
|
+
</BbRadioGroup>
|
|
986
|
+
|
|
987
|
+
<!-- Auto-loading: the button tracks the async handler by itself. -->
|
|
988
|
+
<div>
|
|
989
|
+
<BbButton
|
|
990
|
+
prepend:icon="lucide:send"
|
|
991
|
+
variant="ghost"
|
|
992
|
+
@click="ctx.sendTest"
|
|
993
|
+
>
|
|
994
|
+
Send test email
|
|
995
|
+
</BbButton>
|
|
996
|
+
</div>
|
|
997
|
+
</section>
|
|
998
|
+
</template>
|
|
999
|
+
|
|
1000
|
+
<script setup lang="ts">
|
|
1001
|
+
import { BbButton, BbRadioGroup, BbTextarea, BbTextInput } from 'bitboss-ui';
|
|
1002
|
+
import { TEMPLATES, useCampaignWizard } from './context';
|
|
1003
|
+
|
|
1004
|
+
const ctx = useCampaignWizard();
|
|
1005
|
+
|
|
1006
|
+
const errorsFor = (field: string) => ctx.fieldErrors('content', field);
|
|
1007
|
+
</script>
|
|
1008
|
+
```
|
|
1009
|
+
|
|
1010
|
+
### ScheduleStep.vue
|
|
1011
|
+
|
|
1012
|
+
```vue
|
|
1013
|
+
<!-- CampaignWizard/ScheduleStep.vue -->
|
|
1014
|
+
<template>
|
|
1015
|
+
<section class="flex flex-col gap-4">
|
|
1016
|
+
<div class="flex flex-col gap-0.5">
|
|
1017
|
+
<h2 class="text-base font-semibold">When does it go out?</h2>
|
|
1018
|
+
<p class="text-sm text-gray-500">
|
|
1019
|
+
Send it as soon as you confirm, or pick a date.
|
|
1020
|
+
</p>
|
|
1021
|
+
</div>
|
|
1022
|
+
|
|
1023
|
+
<BbRadioGroup
|
|
1024
|
+
v-model="ctx.draft.sendMode"
|
|
1025
|
+
item-text="label"
|
|
1026
|
+
item-value="value"
|
|
1027
|
+
:items="[
|
|
1028
|
+
{
|
|
1029
|
+
label: 'Send now — goes out as soon as you confirm on Review',
|
|
1030
|
+
value: 'now',
|
|
1031
|
+
},
|
|
1032
|
+
{ label: 'Schedule for later', value: 'scheduled' },
|
|
1033
|
+
]"
|
|
1034
|
+
legend="When to send"
|
|
1035
|
+
name="send-mode"
|
|
1036
|
+
/>
|
|
1037
|
+
|
|
1038
|
+
<!-- No inner BbSmoothHeight: the page-level wrapper's resize observer
|
|
1039
|
+
already animates this region appearing and disappearing. -->
|
|
1040
|
+
<div
|
|
1041
|
+
v-if="ctx.draft.sendMode === 'scheduled'"
|
|
1042
|
+
class="grid gap-4 sm:grid-cols-2"
|
|
1043
|
+
>
|
|
1044
|
+
<BbDatePickerInput
|
|
1045
|
+
v-model="ctx.draft.sendAt"
|
|
1046
|
+
description="Campaigns go out at 9:00 AM local to the timezone you pick."
|
|
1047
|
+
:errors="errorsFor('sendAt')"
|
|
1048
|
+
label="Send date"
|
|
1049
|
+
label-mode="outside"
|
|
1050
|
+
:min="today"
|
|
1051
|
+
required
|
|
1052
|
+
/>
|
|
1053
|
+
<BbSelect
|
|
1054
|
+
v-model="ctx.draft.timezone"
|
|
1055
|
+
:errors="errorsFor('timezone')"
|
|
1056
|
+
item-text="label"
|
|
1057
|
+
item-value="value"
|
|
1058
|
+
:items="TIMEZONES"
|
|
1059
|
+
label="Timezone"
|
|
1060
|
+
label-mode="outside"
|
|
1061
|
+
required
|
|
1062
|
+
/>
|
|
1063
|
+
</div>
|
|
1064
|
+
</section>
|
|
1065
|
+
</template>
|
|
1066
|
+
|
|
1067
|
+
<script setup lang="ts">
|
|
1068
|
+
import { BbDatePickerInput, BbRadioGroup, BbSelect } from 'bitboss-ui';
|
|
1069
|
+
import { TIMEZONES, useCampaignWizard } from './context';
|
|
1070
|
+
|
|
1071
|
+
const ctx = useCampaignWizard();
|
|
1072
|
+
|
|
1073
|
+
const errorsFor = (field: string) => ctx.fieldErrors('schedule', field);
|
|
1074
|
+
|
|
1075
|
+
// BbDatePickerInput's min takes YYYY-MM-DD.
|
|
1076
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
1077
|
+
</script>
|
|
1078
|
+
```
|
|
1079
|
+
|
|
1080
|
+
### ReviewStep.vue
|
|
1081
|
+
|
|
1082
|
+
```vue
|
|
1083
|
+
<!-- CampaignWizard/ReviewStep.vue -->
|
|
1084
|
+
<template>
|
|
1085
|
+
<section class="flex flex-col gap-5">
|
|
1086
|
+
<div class="flex flex-col gap-0.5">
|
|
1087
|
+
<h2 class="text-base font-semibold">Review and send</h2>
|
|
1088
|
+
<p class="text-sm text-gray-500">
|
|
1089
|
+
Everything below is what goes out. Edit any section — your progress is
|
|
1090
|
+
kept.
|
|
1091
|
+
</p>
|
|
1092
|
+
</div>
|
|
1093
|
+
|
|
1094
|
+
<!-- Audience -->
|
|
1095
|
+
<section
|
|
1096
|
+
class="flex flex-col gap-2 rounded-[var(--bb-radius)] border p-3 text-sm"
|
|
1097
|
+
>
|
|
1098
|
+
<div class="flex items-center justify-between gap-2">
|
|
1099
|
+
<h3 class="text-xs font-semibold uppercase tracking-wide text-gray-500">
|
|
1100
|
+
Audience
|
|
1101
|
+
</h3>
|
|
1102
|
+
<BbButton
|
|
1103
|
+
prepend:icon="lucide:pencil"
|
|
1104
|
+
size="sm"
|
|
1105
|
+
variant="ghost"
|
|
1106
|
+
@click="() => ctx.goToStep('audience')"
|
|
1107
|
+
>
|
|
1108
|
+
Edit
|
|
1109
|
+
</BbButton>
|
|
1110
|
+
</div>
|
|
1111
|
+
<dl class="grid grid-cols-[auto_1fr] gap-x-4 gap-y-1.5">
|
|
1112
|
+
<dt class="text-gray-500">Segment</dt>
|
|
1113
|
+
<dd>
|
|
1114
|
+
<BbButton size="sm" variant="link" @click="ctx.peekSegment">
|
|
1115
|
+
{{ ctx.selectedSegment.value?.name ?? '—' }}
|
|
1116
|
+
</BbButton>
|
|
1117
|
+
</dd>
|
|
1118
|
+
<dt class="text-gray-500">Countries</dt>
|
|
1119
|
+
<dd>{{ countryNames || 'All countries' }}</dd>
|
|
1120
|
+
<dt class="text-gray-500">Fatigue filter</dt>
|
|
1121
|
+
<dd>
|
|
1122
|
+
{{
|
|
1123
|
+
ctx.draft.excludeRecent
|
|
1124
|
+
? 'Skipping contacts emailed in the last 7 days'
|
|
1125
|
+
: 'Off'
|
|
1126
|
+
}}
|
|
1127
|
+
</dd>
|
|
1128
|
+
<dt class="text-gray-500">Estimated recipients</dt>
|
|
1129
|
+
<dd class="font-medium">
|
|
1130
|
+
{{
|
|
1131
|
+
ctx.estimatedRecipients.value === null
|
|
1132
|
+
? '—'
|
|
1133
|
+
: number.format(ctx.estimatedRecipients.value)
|
|
1134
|
+
}}
|
|
1135
|
+
</dd>
|
|
1136
|
+
</dl>
|
|
1137
|
+
</section>
|
|
1138
|
+
|
|
1139
|
+
<!-- Content -->
|
|
1140
|
+
<section
|
|
1141
|
+
class="flex flex-col gap-2 rounded-[var(--bb-radius)] border p-3 text-sm"
|
|
1142
|
+
>
|
|
1143
|
+
<div class="flex items-center justify-between gap-2">
|
|
1144
|
+
<h3 class="text-xs font-semibold uppercase tracking-wide text-gray-500">
|
|
1145
|
+
Content
|
|
1146
|
+
</h3>
|
|
1147
|
+
<BbButton
|
|
1148
|
+
prepend:icon="lucide:pencil"
|
|
1149
|
+
size="sm"
|
|
1150
|
+
variant="ghost"
|
|
1151
|
+
@click="() => ctx.goToStep('content')"
|
|
1152
|
+
>
|
|
1153
|
+
Edit
|
|
1154
|
+
</BbButton>
|
|
1155
|
+
</div>
|
|
1156
|
+
<dl class="grid grid-cols-[auto_1fr] gap-x-4 gap-y-1.5">
|
|
1157
|
+
<dt class="text-gray-500">Subject</dt>
|
|
1158
|
+
<dd class="font-medium">{{ ctx.draft.subject }}</dd>
|
|
1159
|
+
<dt class="text-gray-500">Preheader</dt>
|
|
1160
|
+
<dd>{{ ctx.draft.preheader || '—' }}</dd>
|
|
1161
|
+
<dt class="text-gray-500">Template</dt>
|
|
1162
|
+
<dd>{{ templateName }}</dd>
|
|
1163
|
+
<dt class="text-gray-500">Body</dt>
|
|
1164
|
+
<dd class="text-gray-500">{{ bodyExcerpt }}</dd>
|
|
1165
|
+
</dl>
|
|
1166
|
+
</section>
|
|
1167
|
+
|
|
1168
|
+
<!-- Schedule -->
|
|
1169
|
+
<section
|
|
1170
|
+
class="flex flex-col gap-2 rounded-[var(--bb-radius)] border p-3 text-sm"
|
|
1171
|
+
>
|
|
1172
|
+
<div class="flex items-center justify-between gap-2">
|
|
1173
|
+
<h3 class="text-xs font-semibold uppercase tracking-wide text-gray-500">
|
|
1174
|
+
Schedule
|
|
1175
|
+
</h3>
|
|
1176
|
+
<BbButton
|
|
1177
|
+
prepend:icon="lucide:pencil"
|
|
1178
|
+
size="sm"
|
|
1179
|
+
variant="ghost"
|
|
1180
|
+
@click="() => ctx.goToStep('schedule')"
|
|
1181
|
+
>
|
|
1182
|
+
Edit
|
|
1183
|
+
</BbButton>
|
|
1184
|
+
</div>
|
|
1185
|
+
<p>{{ scheduleSummary }}</p>
|
|
1186
|
+
</section>
|
|
1187
|
+
</section>
|
|
1188
|
+
</template>
|
|
1189
|
+
|
|
1190
|
+
<script setup lang="ts">
|
|
1191
|
+
import { computed } from 'vue';
|
|
1192
|
+
import { BbButton } from 'bitboss-ui';
|
|
1193
|
+
import { COUNTRIES, TEMPLATES, TIMEZONES, useCampaignWizard } from './context';
|
|
1194
|
+
|
|
1195
|
+
const ctx = useCampaignWizard();
|
|
1196
|
+
|
|
1197
|
+
const number = new Intl.NumberFormat('en-US');
|
|
1198
|
+
|
|
1199
|
+
const countryNames = computed(() =>
|
|
1200
|
+
ctx.draft.countries
|
|
1201
|
+
.map(
|
|
1202
|
+
(code) =>
|
|
1203
|
+
COUNTRIES.find((country) => country.value === code)?.label ?? code
|
|
1204
|
+
)
|
|
1205
|
+
.join(', ')
|
|
1206
|
+
);
|
|
1207
|
+
|
|
1208
|
+
const templateName = computed(
|
|
1209
|
+
() =>
|
|
1210
|
+
TEMPLATES.find((template) => template.id === ctx.draft.templateId)?.name ??
|
|
1211
|
+
'—'
|
|
1212
|
+
);
|
|
1213
|
+
|
|
1214
|
+
const bodyExcerpt = computed(() =>
|
|
1215
|
+
ctx.draft.body.length > 140
|
|
1216
|
+
? `${ctx.draft.body.slice(0, 140)}…`
|
|
1217
|
+
: ctx.draft.body
|
|
1218
|
+
);
|
|
1219
|
+
|
|
1220
|
+
const scheduleSummary = computed(() => {
|
|
1221
|
+
if (ctx.draft.sendMode === 'now')
|
|
1222
|
+
return 'Sends immediately after you confirm.';
|
|
1223
|
+
if (!ctx.draft.sendAt) return '—';
|
|
1224
|
+
const date = new Date(ctx.draft.sendAt).toLocaleDateString('en-US', {
|
|
1225
|
+
weekday: 'short',
|
|
1226
|
+
month: 'short',
|
|
1227
|
+
day: 'numeric',
|
|
1228
|
+
year: 'numeric',
|
|
1229
|
+
});
|
|
1230
|
+
const zone =
|
|
1231
|
+
TIMEZONES.find((timezone) => timezone.value === ctx.draft.timezone)
|
|
1232
|
+
?.label ?? ctx.draft.timezone;
|
|
1233
|
+
return `Scheduled for ${date} · 9:00 AM · ${zone}`;
|
|
1234
|
+
});
|
|
1235
|
+
</script>
|
|
1236
|
+
```
|
|
1237
|
+
|
|
1238
|
+
### WizardFooter.vue
|
|
1239
|
+
|
|
1240
|
+
```vue
|
|
1241
|
+
<!-- CampaignWizard/WizardFooter.vue -->
|
|
1242
|
+
<template>
|
|
1243
|
+
<footer class="flex items-center justify-between gap-2 border-t pt-4">
|
|
1244
|
+
<!-- Back is always free — never validated, only bounded. -->
|
|
1245
|
+
<BbButton
|
|
1246
|
+
:disabled="isFirst"
|
|
1247
|
+
prepend:icon="lucide:chevron-left"
|
|
1248
|
+
variant="ghost"
|
|
1249
|
+
@click="ctx.goBack"
|
|
1250
|
+
>
|
|
1251
|
+
Back
|
|
1252
|
+
</BbButton>
|
|
1253
|
+
|
|
1254
|
+
<div class="flex items-center gap-2">
|
|
1255
|
+
<!-- On every step, whatever the validation state. -->
|
|
1256
|
+
<BbButton
|
|
1257
|
+
prepend:icon="lucide:save"
|
|
1258
|
+
variant="ghost"
|
|
1259
|
+
@click="ctx.saveDraft"
|
|
1260
|
+
>
|
|
1261
|
+
Save draft
|
|
1262
|
+
</BbButton>
|
|
1263
|
+
|
|
1264
|
+
<BbButton
|
|
1265
|
+
v-if="!isReview"
|
|
1266
|
+
append:icon="lucide:chevron-right"
|
|
1267
|
+
variant="secondary"
|
|
1268
|
+
@click="ctx.goNext"
|
|
1269
|
+
>
|
|
1270
|
+
Next
|
|
1271
|
+
</BbButton>
|
|
1272
|
+
|
|
1273
|
+
<!-- The page's single primary. -->
|
|
1274
|
+
<BbButton
|
|
1275
|
+
v-else
|
|
1276
|
+
:loading="ctx.submitting.value"
|
|
1277
|
+
prepend:icon="lucide:send"
|
|
1278
|
+
variant="primary"
|
|
1279
|
+
@click="ctx.submit"
|
|
1280
|
+
>
|
|
1281
|
+
{{
|
|
1282
|
+
ctx.draft.sendMode === 'now' ? 'Send campaign' : 'Schedule campaign'
|
|
1283
|
+
}}
|
|
1284
|
+
</BbButton>
|
|
1285
|
+
</div>
|
|
1286
|
+
</footer>
|
|
1287
|
+
</template>
|
|
1288
|
+
|
|
1289
|
+
<script setup lang="ts">
|
|
1290
|
+
import { computed } from 'vue';
|
|
1291
|
+
import { BbButton } from 'bitboss-ui';
|
|
1292
|
+
import { STEPS, useCampaignWizard } from './context';
|
|
1293
|
+
|
|
1294
|
+
const ctx = useCampaignWizard();
|
|
1295
|
+
|
|
1296
|
+
const isFirst = computed(() => ctx.step.value === STEPS[0].key);
|
|
1297
|
+
const isReview = computed(() => ctx.step.value === 'review');
|
|
1298
|
+
</script>
|
|
1299
|
+
```
|
|
1300
|
+
|
|
1301
|
+
### SegmentPeek.vue
|
|
1302
|
+
|
|
1303
|
+
```vue
|
|
1304
|
+
<!-- CampaignWizard/SegmentPeek.vue -->
|
|
1305
|
+
<template>
|
|
1306
|
+
<div v-if="segment" class="flex flex-col gap-4 text-sm">
|
|
1307
|
+
<header class="flex flex-col gap-0.5">
|
|
1308
|
+
<h2 class="text-base font-semibold">{{ segment.name }}</h2>
|
|
1309
|
+
<p class="text-xs text-gray-500">
|
|
1310
|
+
{{ number.format(segment.contactCount) }} contacts · last synced
|
|
1311
|
+
{{ longDate(segment.lastSyncedAt) }}
|
|
1312
|
+
</p>
|
|
1313
|
+
</header>
|
|
1314
|
+
|
|
1315
|
+
<!-- Stat strip. -->
|
|
1316
|
+
<div class="grid grid-cols-3 gap-2">
|
|
1317
|
+
<div class="rounded-[var(--bb-radius)] border p-2">
|
|
1318
|
+
<p class="text-xs text-gray-500">Avg. open rate</p>
|
|
1319
|
+
<p class="font-semibold">{{ percent(segment.openRate) }}</p>
|
|
1320
|
+
</div>
|
|
1321
|
+
<div class="rounded-[var(--bb-radius)] border p-2">
|
|
1322
|
+
<p class="text-xs text-gray-500">Avg. click rate</p>
|
|
1323
|
+
<p class="font-semibold">{{ percent(segment.clickRate) }}</p>
|
|
1324
|
+
</div>
|
|
1325
|
+
<div class="rounded-[var(--bb-radius)] border p-2">
|
|
1326
|
+
<p class="text-xs text-gray-500">Unsubscribes / mo</p>
|
|
1327
|
+
<p class="font-semibold">
|
|
1328
|
+
{{ number.format(segment.monthlyUnsubscribes) }}
|
|
1329
|
+
</p>
|
|
1330
|
+
</div>
|
|
1331
|
+
</div>
|
|
1332
|
+
|
|
1333
|
+
<!-- The segment's definition, so "who is in here" is answerable in place. -->
|
|
1334
|
+
<section class="flex flex-col gap-1.5">
|
|
1335
|
+
<h3 class="text-xs font-semibold uppercase tracking-wide text-gray-500">
|
|
1336
|
+
Conditions
|
|
1337
|
+
</h3>
|
|
1338
|
+
<ul class="flex flex-col gap-1">
|
|
1339
|
+
<li v-for="rule in segment.rules" :key="rule" class="flex gap-2">
|
|
1340
|
+
<BbIcon
|
|
1341
|
+
class="mt-0.5 shrink-0 text-gray-400"
|
|
1342
|
+
icon="lucide:filter"
|
|
1343
|
+
size="14"
|
|
1344
|
+
/>
|
|
1345
|
+
<span>{{ rule }}</span>
|
|
1346
|
+
</li>
|
|
1347
|
+
</ul>
|
|
1348
|
+
</section>
|
|
1349
|
+
|
|
1350
|
+
<!-- Recent campaigns to this segment: fatigue check before sending. -->
|
|
1351
|
+
<section class="flex flex-col gap-1.5">
|
|
1352
|
+
<h3 class="text-xs font-semibold uppercase tracking-wide text-gray-500">
|
|
1353
|
+
Recent campaigns
|
|
1354
|
+
</h3>
|
|
1355
|
+
<ul class="flex flex-col">
|
|
1356
|
+
<li
|
|
1357
|
+
v-for="recent in segment.recentCampaigns"
|
|
1358
|
+
:key="recent.id"
|
|
1359
|
+
class="flex items-center justify-between gap-2 border-b py-1.5 last:border-b-0"
|
|
1360
|
+
>
|
|
1361
|
+
<span>{{ recent.name }}</span>
|
|
1362
|
+
<span class="text-xs text-gray-500">{{
|
|
1363
|
+
longDate(recent.sentAt)
|
|
1364
|
+
}}</span>
|
|
1365
|
+
</li>
|
|
1366
|
+
</ul>
|
|
1367
|
+
</section>
|
|
1368
|
+
|
|
1369
|
+
<footer class="mt-2 flex items-center gap-2 border-t pt-3">
|
|
1370
|
+
<BbButton
|
|
1371
|
+
append:icon="lucide:arrow-up-right"
|
|
1372
|
+
size="sm"
|
|
1373
|
+
:to="`/segments/${segment.id}`"
|
|
1374
|
+
variant="ghost"
|
|
1375
|
+
>
|
|
1376
|
+
Open segment
|
|
1377
|
+
</BbButton>
|
|
1378
|
+
</footer>
|
|
1379
|
+
</div>
|
|
1380
|
+
|
|
1381
|
+
<div v-else class="flex justify-center p-8">
|
|
1382
|
+
<BbSpinner />
|
|
1383
|
+
</div>
|
|
1384
|
+
</template>
|
|
1385
|
+
|
|
1386
|
+
<script setup lang="ts">
|
|
1387
|
+
import { ref, watchEffect } from 'vue';
|
|
1388
|
+
import { BbButton, BbIcon, BbSpinner } from 'bitboss-ui';
|
|
1389
|
+
import type { SegmentDetail } from '@/types';
|
|
1390
|
+
|
|
1391
|
+
const props = defineProps<{ segmentId: number }>();
|
|
1392
|
+
|
|
1393
|
+
const segment = ref<SegmentDetail | null>(null);
|
|
1394
|
+
watchEffect(async () => {
|
|
1395
|
+
segment.value = await $fetch<SegmentDetail>(
|
|
1396
|
+
`/api/segments/${props.segmentId}/detail`
|
|
1397
|
+
);
|
|
1398
|
+
});
|
|
1399
|
+
|
|
1400
|
+
const number = new Intl.NumberFormat('en-US');
|
|
1401
|
+
const percent = (value: number) => `${(value * 100).toFixed(1)}%`;
|
|
1402
|
+
const longDate = (value: string) =>
|
|
1403
|
+
new Date(value).toLocaleDateString('en-US', {
|
|
1404
|
+
month: 'short',
|
|
1405
|
+
day: 'numeric',
|
|
1406
|
+
year: 'numeric',
|
|
1407
|
+
});
|
|
1408
|
+
</script>
|
|
1409
|
+
```
|
|
1410
|
+
|
|
1411
|
+
## Why it is built this way
|
|
1412
|
+
|
|
1413
|
+
- **The context coopts, leaves consume.** `shell` is joined once in
|
|
1414
|
+
`provideCampaignWizard()`; seven components share the draft, the step
|
|
1415
|
+
machine, validation and every action through one `useCampaignWizard()`
|
|
1416
|
+
call. The only data prop on the page is `SegmentPeek`'s `segment-id`;
|
|
1417
|
+
no component receives a callback.
|
|
1418
|
+
- **One gating predicate rules everything.** `unlocked(step)` — every
|
|
1419
|
+
step before it validates — drives the stepper's `upcoming`/`locked`
|
|
1420
|
+
states, `goToStep`'s guard, and (via `issues[current]`) `goNext`. A
|
|
1421
|
+
stepper jump is therefore exactly as safe as pressing Next the right
|
|
1422
|
+
number of times; the header can never smuggle the user past validation.
|
|
1423
|
+
Only steps already passed count as `done` — a defaulted step (Schedule
|
|
1424
|
+
starts valid on "send now") stays numbered (`upcoming`), not
|
|
1425
|
+
checkmarked, until the user actually moves past it, though it is
|
|
1426
|
+
jumpable straight away: there is genuinely nothing left to do there.
|
|
1427
|
+
- **`FetchError` is the error contract for saves.** `statusCode === 422`
|
|
1428
|
+
means field errors — `applyServerErrors` merges `error.data.errors` into
|
|
1429
|
+
`serverErrors`, which `issues` folds onto the owning step, so a failed
|
|
1430
|
+
save renders in the exact same `BbAlert` summary and inline errors as a
|
|
1431
|
+
client-side block, and jumps the user to the first broken step. Live
|
|
1432
|
+
issues, deferred surfacing still holds: errors render only for
|
|
1433
|
+
`attempted` steps, and shrink as each fix lands.
|
|
1434
|
+
- **The step lives in the URL, the draft lives on the server.** `?step=3`
|
|
1435
|
+
mirrors via `navigateTo({ query }, { replace: true })` — no extra
|
|
1436
|
+
history entry; `useAsyncData` re-fetches the saved draft on load, and
|
|
1437
|
+
the requested step is clamped to the first incomplete one, so a stale or
|
|
1438
|
+
forged link degrades to the right place instead of an unusable locked
|
|
1439
|
+
screen.
|
|
1440
|
+
- **Steps are four components, not one with a `type` prop.** Each step
|
|
1441
|
+
owns its inputs, its formatting helpers, and nothing else; the assembly
|
|
1442
|
+
is a `v-if` chain inside the `BbSmoothHeight` outlet. A single
|
|
1443
|
+
`WizardStep` with a renderer switch is the API-monster seed.
|
|
1444
|
+
- **`BbBaseButton` is the right stepper primitive.** The steps are
|
|
1445
|
+
clickable surfaces whose look is entirely bespoke — token-styled pills
|
|
1446
|
+
with a number/check bullet — and the primitive contributes what CSS
|
|
1447
|
+
can't: a real `<button>`, native `disabled` for locked steps, and a
|
|
1448
|
+
focus scaffold (the visible ring is ours, per its guide). `aria-current="step"`
|
|
1449
|
+
is set manually — the prop-based `ariaCurrentValue` only applies to
|
|
1450
|
+
exact-active _links_.
|
|
1451
|
+
- **One `BbSmoothHeight`, wrapping alert + outlet.** Both height changes
|
|
1452
|
+
on this page — the step swap and the summary toggling — animate through
|
|
1453
|
+
the same wrapper, so they compose into one glide when Next is blocked.
|
|
1454
|
+
Regions that appear _inside_ a step (the scheduled-date grid) are caught
|
|
1455
|
+
by the same resize observer; and the body textarea keeps fixed `rows`
|
|
1456
|
+
because an auto-growing textarea inside the wrapper would re-animate the
|
|
1457
|
+
outlet on every line break. Height motion is enabled only after mount
|
|
1458
|
+
(`:disabled` until a 300ms `onMounted` flag flips), so the first paint
|
|
1459
|
+
never animates in.
|
|
1460
|
+
- **The guard, submit and discard share one snapshot.** `dirty` is a JSON
|
|
1461
|
+
compare against the last save; `submit` and `discardDraft` reset the
|
|
1462
|
+
snapshot before navigating so `onBeforeRouteLeave` (works unchanged in
|
|
1463
|
+
Nuxt) never re-asks about changes that were just sent or deliberately
|
|
1464
|
+
destroyed.
|
|
1465
|
+
- **Sizing:** everything follows `md`-first — footer, topbar and step
|
|
1466
|
+
forms use default-size buttons and regular inputs; `sm` appears only on
|
|
1467
|
+
the dense inline surfaces (Review's Edit/segment links, the peek
|
|
1468
|
+
footer). One `variant="primary"` (Send/Schedule); Next is `secondary`;
|
|
1469
|
+
Save draft, Back and Send test are ghosts; the two destructive actions
|
|
1470
|
+
(discard, leave-with-changes) both ride `useConfirm`.
|
|
1471
|
+
|
|
1472
|
+
## Variations
|
|
1473
|
+
|
|
1474
|
+
- **Branching steps**: make `STEPS` a computed — e.g. an A/B test
|
|
1475
|
+
campaign inserts a "Variants" step after Content. The status predicate,
|
|
1476
|
+
URL mapping and footer already work off the array, so nothing else
|
|
1477
|
+
changes.
|
|
1478
|
+
- **Autosave**: debounce a watcher on `draft` that calls a silent
|
|
1479
|
+
`saveDraft` (no toast); the footer button and the topbar save-state stay
|
|
1480
|
+
as the manual escape, and the leave guard almost never fires.
|
|
1481
|
+
- **Vertical checklist stepper** (Mailchimp's actual layout): swap
|
|
1482
|
+
`StepperHeader` for a left rail where each step row also shows its
|
|
1483
|
+
one-line summary and the step bodies expand in place — the context and
|
|
1484
|
+
its predicate are unchanged.
|
|
1485
|
+
- **Send time**: add an hour `BbSelect` beside the timezone and fold it
|
|
1486
|
+
into `sendAt` on save, replacing the fixed 9:00 AM policy.
|