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,1418 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Wizard Form (Vue)
|
|
3
|
+
summary: Mailchimp-grade 4-step campaign wizard — BbBaseButton stepper with done/current/upcoming/locked states, per-step validation gating Next behind a BbAlert summary, BbSmoothHeight step swaps, the step in the URL, save-draft everywhere and a dirty route-leave guard, all through one coopting page context.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Recipe: Wizard Form (Vue)
|
|
7
|
+
|
|
8
|
+
A Mailchimp-grade "Create campaign" wizard for a **plain Vue SPA with
|
|
9
|
+
vue-router**: four steps (Audience → Content → Schedule → Review) behind a
|
|
10
|
+
custom stepper header, per-step validation gating forward progress, the
|
|
11
|
+
active step mirrored into the URL, save-draft everywhere, and a dirty-state
|
|
12
|
+
leave guard — all driven by one coopting page context so every leaf
|
|
13
|
+
component takes zero props beyond the id the segment peek renders.
|
|
14
|
+
|
|
15
|
+
Sibling recipes: [Nuxt](../nuxt/wizard-form.md) ·
|
|
16
|
+
[Inertia](../inertia/wizard-form.md).
|
|
17
|
+
|
|
18
|
+
## Prerequisites
|
|
19
|
+
|
|
20
|
+
- `<BbConfirm />` and `<BbToast />` mounted once in the app root.
|
|
21
|
+
- [App Layout](../../guides/app-layout.md) and
|
|
22
|
+
[Page Shell](../../guides/page-shell.md) copied into
|
|
23
|
+
`src/components/layout/` per their guides — the page renders inside this
|
|
24
|
+
chrome.
|
|
25
|
+
|
|
26
|
+
## Key ideas
|
|
27
|
+
|
|
28
|
+
- **One coopting page context.** `provideCampaignWizard()` joins the
|
|
29
|
+
page-shell stack once and owns the draft, the current step, the live
|
|
30
|
+
validation, and every action. All seven leaves call
|
|
31
|
+
`useCampaignWizard()` and nothing else — the only prop on the page is
|
|
32
|
+
the id the segment peek renders.
|
|
33
|
+
- **The stepper is derived state, not navigation chrome.** A step is
|
|
34
|
+
reachable exactly when every step before it validates — the same
|
|
35
|
+
predicate `goNext()` uses — so jumping from the header can never outrun
|
|
36
|
+
the validation gate. Steps already passed show `done` (checkmarked),
|
|
37
|
+
reachable future steps stay numbered (`upcoming`), and `locked` steps
|
|
38
|
+
render as disabled `BbBaseButton`s.
|
|
39
|
+
- **Validation is computed live, surfaced on demand.** `issues`
|
|
40
|
+
recomputes on every keystroke, but the `BbAlert` summary and the inline
|
|
41
|
+
field errors appear only after a blocked Next (`attempted`), and shrink
|
|
42
|
+
live as the user fixes each problem.
|
|
43
|
+
- **The step lives in the URL, the draft lives on the server.** `?step=3`
|
|
44
|
+
is mirrored with `router.replace`; on refresh the saved draft is
|
|
45
|
+
re-fetched and the requested step is clamped to the first incomplete
|
|
46
|
+
one, so a shared link can never open a locked step.
|
|
47
|
+
- **One `BbSmoothHeight` wraps the alert + step outlet together**, so the
|
|
48
|
+
step swap _and_ the validation summary appearing both glide instead of
|
|
49
|
+
snapping. Conditional regions inside a step (the scheduled-date fields)
|
|
50
|
+
need no wrapper of their own — the same resize observer catches them.
|
|
51
|
+
- **Back and Save draft are always free; validation only guards forward.**
|
|
52
|
+
The page's one `variant="primary"` is the final Send/Schedule submit,
|
|
53
|
+
with `:loading` riding `ctx.submitting`.
|
|
54
|
+
- **Leaving is guarded by a snapshot compare.** `dirty` is a JSON diff
|
|
55
|
+
against the last saved draft; `onBeforeRouteLeave` + `useConfirm` ask
|
|
56
|
+
before losing changes, and both submit and discard reset the snapshot so
|
|
57
|
+
they never double-prompt.
|
|
58
|
+
|
|
59
|
+
## Page anatomy
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
CampaignWizard/
|
|
63
|
+
Index.vue ← assembly: PageShell slots, stepper, BbSmoothHeight outlet, footer
|
|
64
|
+
context.ts ← coopting page context (shell + draft + step + validation + actions)
|
|
65
|
+
StepperHeader.vue ← BbBaseButton steps with done/current/upcoming/locked states
|
|
66
|
+
AudienceStep.vue ← segment select, audience filters, live recipient estimate
|
|
67
|
+
ContentStep.vue ← subject/preheader, body, template picker, send-test
|
|
68
|
+
ScheduleStep.vue ← send-now vs scheduled, date + timezone
|
|
69
|
+
ReviewStep.vue ← read-only summary with per-section Edit links
|
|
70
|
+
WizardFooter.vue ← Back · Save draft · Next / Send with :loading
|
|
71
|
+
SegmentPeek.vue ← sidebar workbench for the selected segment
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Chrome assumed from the guides: [App Layout](../../guides/app-layout.md)
|
|
75
|
+
(left nav + fat footer) and [Page Shell](../../guides/page-shell.md).
|
|
76
|
+
Wizard archetype: no filter rail, no tabs, no table, no pagination, no bulk
|
|
77
|
+
bar — the work surface is the step outlet.
|
|
78
|
+
|
|
79
|
+
## Action inventory
|
|
80
|
+
|
|
81
|
+
| # | Action | Trigger | Mechanism |
|
|
82
|
+
| --- | --------------------------- | -------------------------------- | -------------------------------------------------------------- |
|
|
83
|
+
| 1 | Advance a step (validated) | footer **Next** (secondary) | `goNext()` — blocks and raises the `BbAlert` summary on issues |
|
|
84
|
+
| 2 | Go back | footer **Back** (ghost) | `goBack()` — never validated |
|
|
85
|
+
| 3 | Jump to an unlocked step | stepper header | `goToStep()`; locked steps are disabled `BbBaseButton`s |
|
|
86
|
+
| 4 | Edit a section from Review | per-section **Edit** links | `goToStep()` back to that step |
|
|
87
|
+
| 5 | Save draft | footer ghost, on every step | `saveDraft()` → API + snapshot reset + toast |
|
|
88
|
+
| 6 | Send a test email | Content step ghost | `sendTest()` — validates content first, auto-loading button |
|
|
89
|
+
| 7 | Send / schedule campaign | Review, the page's one `primary` | `submit()` with `:loading`, whole-wizard validation |
|
|
90
|
+
| 8 | Discard draft | topbar, destructive | `useConfirm` → delete + navigate to `/campaigns` |
|
|
91
|
+
| 9 | Peek the segment | segment name (Audience & Review) | `peekSegment()` → shell stack |
|
|
92
|
+
| 10 | Leave with unsaved changes | any route navigation | `onBeforeRouteLeave` guard → `useConfirm` |
|
|
93
|
+
| 11 | Restore position on refresh | URL `?step=N` | step mirrored via `router.replace`, clamped on load |
|
|
94
|
+
|
|
95
|
+
## The code
|
|
96
|
+
|
|
97
|
+
### context.ts
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
// CampaignWizard/context.ts
|
|
101
|
+
import {
|
|
102
|
+
computed,
|
|
103
|
+
inject,
|
|
104
|
+
provide,
|
|
105
|
+
reactive,
|
|
106
|
+
ref,
|
|
107
|
+
watch,
|
|
108
|
+
watchEffect,
|
|
109
|
+
type ComputedRef,
|
|
110
|
+
type InjectionKey,
|
|
111
|
+
type Ref,
|
|
112
|
+
} from 'vue';
|
|
113
|
+
import { onBeforeRouteLeave, useRoute, useRouter } from 'vue-router';
|
|
114
|
+
import { useConfirm, useToast } from 'bitboss-ui';
|
|
115
|
+
import {
|
|
116
|
+
usePageShellContext,
|
|
117
|
+
type PageShellContext,
|
|
118
|
+
} from '@/components/layout/usePageShellContext';
|
|
119
|
+
import { api } from '@/api';
|
|
120
|
+
import type { Campaign, Segment } from '@/types';
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* What the sidebar can show: the audience segment behind the campaign.
|
|
124
|
+
* `title` is what `PageShell`'s `#sidebar-title` slot renders — without it the
|
|
125
|
+
* peek opens with an empty header.
|
|
126
|
+
*/
|
|
127
|
+
export type PeekItem = { type: 'segment'; id: number; title: string };
|
|
128
|
+
|
|
129
|
+
export type StepKey = 'audience' | 'content' | 'schedule' | 'review';
|
|
130
|
+
export type StepStatus = 'done' | 'current' | 'upcoming' | 'locked';
|
|
131
|
+
export type SendMode = 'now' | 'scheduled';
|
|
132
|
+
|
|
133
|
+
export const STEPS: Array<{ key: StepKey; label: string }> = [
|
|
134
|
+
{ key: 'audience', label: 'Audience' },
|
|
135
|
+
{ key: 'content', label: 'Content' },
|
|
136
|
+
{ key: 'schedule', label: 'Schedule' },
|
|
137
|
+
{ key: 'review', label: 'Review' },
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
export interface CampaignTemplate {
|
|
141
|
+
id: string;
|
|
142
|
+
name: string;
|
|
143
|
+
description: string;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export const TEMPLATES: CampaignTemplate[] = [
|
|
147
|
+
{
|
|
148
|
+
id: 'product-update',
|
|
149
|
+
name: 'Product update',
|
|
150
|
+
description: 'Hero image, feature blocks, one CTA.',
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
id: 'newsletter',
|
|
154
|
+
name: 'Newsletter',
|
|
155
|
+
description: 'Multi-story digest with section dividers.',
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
id: 'plain-text',
|
|
159
|
+
name: 'Plain text',
|
|
160
|
+
description: 'No chrome — reads like a personal email.',
|
|
161
|
+
},
|
|
162
|
+
];
|
|
163
|
+
|
|
164
|
+
export const TIMEZONES = [
|
|
165
|
+
{ label: 'Rome (CET)', value: 'Europe/Rome' },
|
|
166
|
+
{ label: 'London (GMT)', value: 'Europe/London' },
|
|
167
|
+
{ label: 'New York (ET)', value: 'America/New_York' },
|
|
168
|
+
{ label: 'Chicago (CT)', value: 'America/Chicago' },
|
|
169
|
+
{ label: 'Los Angeles (PT)', value: 'America/Los_Angeles' },
|
|
170
|
+
{ label: 'Tokyo (JST)', value: 'Asia/Tokyo' },
|
|
171
|
+
{ label: 'Sydney (AEST)', value: 'Australia/Sydney' },
|
|
172
|
+
];
|
|
173
|
+
|
|
174
|
+
export const COUNTRIES = [
|
|
175
|
+
{ label: 'Italy', value: 'IT' },
|
|
176
|
+
{ label: 'France', value: 'FR' },
|
|
177
|
+
{ label: 'Germany', value: 'DE' },
|
|
178
|
+
{ label: 'United Kingdom', value: 'GB' },
|
|
179
|
+
{ label: 'United States', value: 'US' },
|
|
180
|
+
{ label: 'Canada', value: 'CA' },
|
|
181
|
+
];
|
|
182
|
+
|
|
183
|
+
/** Everything the four steps edit. Persisted verbatim by saveDraft/submit. */
|
|
184
|
+
export interface CampaignDraft {
|
|
185
|
+
// Audience
|
|
186
|
+
segmentId: number | null;
|
|
187
|
+
/** Empty array = every country in the segment. */
|
|
188
|
+
countries: string[];
|
|
189
|
+
/** Skip contacts already emailed in the last 7 days. */
|
|
190
|
+
excludeRecent: boolean;
|
|
191
|
+
// Content
|
|
192
|
+
subject: string;
|
|
193
|
+
preheader: string;
|
|
194
|
+
body: string;
|
|
195
|
+
templateId: string | null;
|
|
196
|
+
// Schedule
|
|
197
|
+
sendMode: SendMode;
|
|
198
|
+
/** ISO string from BbDatePickerInput; only meaningful when scheduled. */
|
|
199
|
+
sendAt: string | null;
|
|
200
|
+
timezone: string;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export interface StepIssue {
|
|
204
|
+
/** Draft field the issue belongs to — leaves match on it for inline errors. */
|
|
205
|
+
field: string;
|
|
206
|
+
message: string;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export interface CampaignWizardContext {
|
|
210
|
+
// -- Coopted contexts ------------------------------------------------------
|
|
211
|
+
shell: PageShellContext<PeekItem>;
|
|
212
|
+
// -- Wizard state ----------------------------------------------------------
|
|
213
|
+
loading: Ref<boolean>;
|
|
214
|
+
campaign: Ref<Campaign | null>;
|
|
215
|
+
draft: CampaignDraft;
|
|
216
|
+
step: Ref<StepKey>;
|
|
217
|
+
dirty: ComputedRef<boolean>;
|
|
218
|
+
lastSavedAt: Ref<string | null>;
|
|
219
|
+
submitting: Ref<boolean>;
|
|
220
|
+
// -- Validation ------------------------------------------------------------
|
|
221
|
+
issues: ComputedRef<Record<StepKey, StepIssue[]>>;
|
|
222
|
+
/** Steps whose Next was blocked at least once — only they show errors. */
|
|
223
|
+
attempted: Record<StepKey, boolean>;
|
|
224
|
+
stepStatus: (key: StepKey) => StepStatus;
|
|
225
|
+
fieldErrors: (step: StepKey, field: string) => string[];
|
|
226
|
+
// -- Audience intel --------------------------------------------------------
|
|
227
|
+
selectedSegment: Ref<Segment | null>;
|
|
228
|
+
estimatedRecipients: Ref<number | null>;
|
|
229
|
+
// -- Peek grammar ----------------------------------------------------------
|
|
230
|
+
peekSegment: () => void;
|
|
231
|
+
// -- Actions (complete: API + state + toast) --------------------------------
|
|
232
|
+
goToStep: (key: StepKey) => void;
|
|
233
|
+
goBack: () => void;
|
|
234
|
+
goNext: () => void;
|
|
235
|
+
saveDraft: () => Promise<void>;
|
|
236
|
+
sendTest: () => Promise<void>;
|
|
237
|
+
submit: () => Promise<void>;
|
|
238
|
+
discardDraft: () => Promise<void>;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const key: InjectionKey<CampaignWizardContext> = Symbol('campaign-wizard');
|
|
242
|
+
|
|
243
|
+
export function provideCampaignWizard(): CampaignWizardContext {
|
|
244
|
+
const { toast } = useToast();
|
|
245
|
+
const { confirm } = useConfirm();
|
|
246
|
+
const route = useRoute();
|
|
247
|
+
const router = useRouter();
|
|
248
|
+
|
|
249
|
+
// -- Coopt the specific contexts: once, here, for the whole page. ----------
|
|
250
|
+
const shell = usePageShellContext<PeekItem>();
|
|
251
|
+
|
|
252
|
+
// -- Wizard state -----------------------------------------------------------
|
|
253
|
+
// Route: `{ path: '/campaigns/:id/edit', component: … }` — the param name
|
|
254
|
+
// here must match the one your route declares.
|
|
255
|
+
const campaignId = Number(route.params.id);
|
|
256
|
+
const campaign = ref<Campaign | null>(null);
|
|
257
|
+
const loading = ref(true);
|
|
258
|
+
|
|
259
|
+
const draft = reactive<CampaignDraft>({
|
|
260
|
+
segmentId: null,
|
|
261
|
+
countries: [],
|
|
262
|
+
excludeRecent: true,
|
|
263
|
+
subject: '',
|
|
264
|
+
preheader: '',
|
|
265
|
+
body: '',
|
|
266
|
+
templateId: null,
|
|
267
|
+
sendMode: 'now',
|
|
268
|
+
sendAt: null,
|
|
269
|
+
timezone: 'Europe/Rome',
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// -- Validation ---------------------------------------------------------------
|
|
273
|
+
// Live on every keystroke; SURFACED only per `attempted` (set when a Next
|
|
274
|
+
// or submit is blocked), so untouched steps never open covered in red.
|
|
275
|
+
const issues = computed<Record<StepKey, StepIssue[]>>(() => {
|
|
276
|
+
const audience: StepIssue[] = [];
|
|
277
|
+
if (draft.segmentId === null)
|
|
278
|
+
audience.push({
|
|
279
|
+
field: 'segmentId',
|
|
280
|
+
message: 'Choose the segment this campaign goes to.',
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
const content: StepIssue[] = [];
|
|
284
|
+
if (!draft.subject.trim())
|
|
285
|
+
content.push({ field: 'subject', message: 'Write a subject line.' });
|
|
286
|
+
else if (draft.subject.length > 150)
|
|
287
|
+
content.push({
|
|
288
|
+
field: 'subject',
|
|
289
|
+
message: 'Keep the subject under 150 characters.',
|
|
290
|
+
});
|
|
291
|
+
if (!draft.body.trim())
|
|
292
|
+
content.push({
|
|
293
|
+
field: 'body',
|
|
294
|
+
message: 'The email body cannot be empty.',
|
|
295
|
+
});
|
|
296
|
+
if (draft.templateId === null)
|
|
297
|
+
content.push({ field: 'templateId', message: 'Pick a template.' });
|
|
298
|
+
|
|
299
|
+
const schedule: StepIssue[] = [];
|
|
300
|
+
if (draft.sendMode === 'scheduled') {
|
|
301
|
+
if (!draft.sendAt)
|
|
302
|
+
schedule.push({ field: 'sendAt', message: 'Pick a send date.' });
|
|
303
|
+
if (!draft.timezone)
|
|
304
|
+
schedule.push({ field: 'timezone', message: 'Pick a timezone.' });
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return { audience, content, schedule, review: [] };
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
const attempted = reactive<Record<StepKey, boolean>>({
|
|
311
|
+
audience: false,
|
|
312
|
+
content: false,
|
|
313
|
+
schedule: false,
|
|
314
|
+
review: false,
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
const fieldErrors = (stepKey: StepKey, field: string): string[] =>
|
|
318
|
+
attempted[stepKey]
|
|
319
|
+
? issues.value[stepKey]
|
|
320
|
+
.filter((issue) => issue.field === field)
|
|
321
|
+
.map((issue) => issue.message)
|
|
322
|
+
: [];
|
|
323
|
+
|
|
324
|
+
// -- Step machine ---------------------------------------------------------------
|
|
325
|
+
const step = ref<StepKey>('audience');
|
|
326
|
+
|
|
327
|
+
const indexOf = (stepKey: StepKey) =>
|
|
328
|
+
STEPS.findIndex((item) => item.key === stepKey);
|
|
329
|
+
|
|
330
|
+
// The single gating predicate: a step is reachable when every step
|
|
331
|
+
// before it validates. Next, the stepper, and submit all derive from it.
|
|
332
|
+
const unlocked = (stepKey: StepKey) =>
|
|
333
|
+
STEPS.slice(0, indexOf(stepKey)).every(
|
|
334
|
+
(item) => issues.value[item.key].length === 0
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
const stepStatus = (stepKey: StepKey): StepStatus => {
|
|
338
|
+
if (stepKey === step.value) return 'current';
|
|
339
|
+
// Only steps already passed count as done — unlocked future steps stay
|
|
340
|
+
// numbered (upcoming), not checkmarked.
|
|
341
|
+
if (indexOf(stepKey) < indexOf(step.value)) return 'done';
|
|
342
|
+
return unlocked(stepKey) ? 'upcoming' : 'locked';
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
const firstIncompleteStep = (): StepKey =>
|
|
346
|
+
STEPS.find((item) => issues.value[item.key].length > 0)?.key ?? 'review';
|
|
347
|
+
|
|
348
|
+
const goToStep = (stepKey: StepKey) => {
|
|
349
|
+
if (stepKey !== step.value && stepStatus(stepKey) === 'locked') return;
|
|
350
|
+
step.value = stepKey;
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
const goBack = () => {
|
|
354
|
+
const previous = STEPS[indexOf(step.value) - 1];
|
|
355
|
+
if (previous) step.value = previous.key;
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
const goNext = () => {
|
|
359
|
+
const current = step.value;
|
|
360
|
+
if (issues.value[current].length > 0) {
|
|
361
|
+
attempted[current] = true; // raises the alert + inline errors
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
attempted[current] = false;
|
|
365
|
+
const next = STEPS[indexOf(current) + 1];
|
|
366
|
+
if (next) step.value = next.key;
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
// Mirror the step into the URL so refresh and shared links restore it.
|
|
370
|
+
watch(step, (next) => {
|
|
371
|
+
void router.replace({
|
|
372
|
+
query: { ...route.query, step: String(indexOf(next) + 1) },
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
// -- Audience intel ---------------------------------------------------------------
|
|
377
|
+
// The select's v-model holds only the id; Review and the summary box need
|
|
378
|
+
// the record. Same resolve-by-id pattern as the flagship's customer pill.
|
|
379
|
+
const selectedSegment = ref<Segment | null>(null);
|
|
380
|
+
watchEffect(async () => {
|
|
381
|
+
selectedSegment.value =
|
|
382
|
+
draft.segmentId === null ? null : await api.segments.get(draft.segmentId);
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
const estimatedRecipients = ref<number | null>(null);
|
|
386
|
+
watchEffect(async () => {
|
|
387
|
+
if (draft.segmentId === null) {
|
|
388
|
+
estimatedRecipients.value = null;
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
estimatedRecipients.value = await api.segments.estimate(draft.segmentId, {
|
|
392
|
+
countries: [...draft.countries],
|
|
393
|
+
excludeRecent: draft.excludeRecent,
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
// -- Peek grammar ----------------------------------------------------------------
|
|
398
|
+
const peekSegment = () => {
|
|
399
|
+
if (draft.segmentId !== null)
|
|
400
|
+
shell.push({ type: 'segment', id: draft.segmentId }, true);
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
// -- Persistence + dirty guard ------------------------------------------------------
|
|
404
|
+
const snapshot = ref('');
|
|
405
|
+
const lastSavedAt = ref<string | null>(null);
|
|
406
|
+
const dirty = computed(
|
|
407
|
+
() => !loading.value && JSON.stringify(draft) !== snapshot.value
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
// Registered here because provideCampaignWizard() runs in the setup of the
|
|
411
|
+
// route component (Index.vue) — exactly where vue-router allows it.
|
|
412
|
+
onBeforeRouteLeave(async () => {
|
|
413
|
+
if (!dirty.value) return true;
|
|
414
|
+
return confirm({
|
|
415
|
+
variant: 'destructive',
|
|
416
|
+
title: 'Leave campaign setup?',
|
|
417
|
+
text: 'You have unsaved changes. Leave and lose them, or stay and save the draft.',
|
|
418
|
+
yes: 'Leave',
|
|
419
|
+
no: 'Keep editing',
|
|
420
|
+
});
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
// The client gate only guards FORWARD navigation — it never blocks this call.
|
|
424
|
+
// The endpoint, though, runs the same whole-draft validation the final submit
|
|
425
|
+
// runs, so saving a half-filled draft rejects with 422. Handle it: an
|
|
426
|
+
// unhandled rejection here surfaces as a console error and the user gets no
|
|
427
|
+
// feedback at all.
|
|
428
|
+
const saveDraft = async () => {
|
|
429
|
+
try {
|
|
430
|
+
await api.campaigns.update(campaignId, { ...draft });
|
|
431
|
+
} catch (error) {
|
|
432
|
+
const err = error as { status?: number };
|
|
433
|
+
if (err.status === 422) {
|
|
434
|
+
toast({
|
|
435
|
+
title: 'Finish the highlighted fields before saving.',
|
|
436
|
+
variant: 'destructive',
|
|
437
|
+
});
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
toast({
|
|
441
|
+
title: 'Something went wrong. Try again.',
|
|
442
|
+
variant: 'destructive',
|
|
443
|
+
});
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
snapshot.value = JSON.stringify(draft);
|
|
447
|
+
lastSavedAt.value = new Date().toISOString();
|
|
448
|
+
toast({ title: 'Draft saved.', variant: 'success' });
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
const sendTest = async () => {
|
|
452
|
+
if (issues.value.content.length > 0) {
|
|
453
|
+
attempted.content = true; // same surfacing as a blocked Next
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
await api.campaigns.sendTest(campaignId, { ...draft });
|
|
457
|
+
toast({ title: 'Test email sent to your address.', variant: 'success' });
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
const submitting = ref(false);
|
|
461
|
+
const submit = async () => {
|
|
462
|
+
// Whole-wizard validation: land the user on the first broken step.
|
|
463
|
+
const incomplete = STEPS.find((item) => issues.value[item.key].length > 0);
|
|
464
|
+
if (incomplete) {
|
|
465
|
+
attempted[incomplete.key] = true;
|
|
466
|
+
step.value = incomplete.key;
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
submitting.value = true;
|
|
470
|
+
try {
|
|
471
|
+
await api.campaigns.update(campaignId, { ...draft });
|
|
472
|
+
await api.campaigns.send(campaignId);
|
|
473
|
+
snapshot.value = JSON.stringify(draft); // the guard has nothing left to protect
|
|
474
|
+
toast({
|
|
475
|
+
title:
|
|
476
|
+
draft.sendMode === 'now'
|
|
477
|
+
? 'Campaign is on its way.'
|
|
478
|
+
: 'Campaign scheduled.',
|
|
479
|
+
variant: 'success',
|
|
480
|
+
});
|
|
481
|
+
await router.push('/campaigns');
|
|
482
|
+
} finally {
|
|
483
|
+
submitting.value = false;
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
const discardDraft = async () => {
|
|
488
|
+
const ok = await confirm({
|
|
489
|
+
variant: 'destructive',
|
|
490
|
+
title: 'Discard draft',
|
|
491
|
+
text: `Delete "${campaign.value?.name ?? 'this campaign'}" and everything entered so far? This cannot be undone.`,
|
|
492
|
+
yes: 'Discard',
|
|
493
|
+
no: 'Keep draft',
|
|
494
|
+
});
|
|
495
|
+
if (!ok) return;
|
|
496
|
+
snapshot.value = JSON.stringify(draft); // don't double-prompt via the leave guard
|
|
497
|
+
await api.campaigns.delete(campaignId);
|
|
498
|
+
toast({ title: 'Draft discarded.', variant: 'success' });
|
|
499
|
+
await router.push('/campaigns');
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
// -- Load ------------------------------------------------------------------------
|
|
503
|
+
// The wizard edits a server-side draft (the list page created it before
|
|
504
|
+
// navigating here, Mailchimp-style), so a refresh restores every field —
|
|
505
|
+
// and ?step restores the position, clamped so it can't open a locked step.
|
|
506
|
+
const initialize = async () => {
|
|
507
|
+
// `get` resolves `null` for an unknown id (a hand-typed URL), so it cannot
|
|
508
|
+
// be destructured blind — that both fails to type-check and throws at
|
|
509
|
+
// runtime. Baseline the snapshot either way, otherwise an empty draft
|
|
510
|
+
// reads as "unsaved changes" and the leave guard fires on a dead page.
|
|
511
|
+
const loaded = await api.campaigns.get(campaignId);
|
|
512
|
+
if (loaded) {
|
|
513
|
+
campaign.value = loaded.campaign;
|
|
514
|
+
Object.assign(draft, loaded.draft);
|
|
515
|
+
}
|
|
516
|
+
snapshot.value = JSON.stringify(draft);
|
|
517
|
+
const requested = STEPS[Number(route.query.step) - 1]?.key ?? 'audience';
|
|
518
|
+
step.value =
|
|
519
|
+
stepStatus(requested) === 'locked' ? firstIncompleteStep() : requested;
|
|
520
|
+
loading.value = false;
|
|
521
|
+
};
|
|
522
|
+
void initialize();
|
|
523
|
+
|
|
524
|
+
const ctx: CampaignWizardContext = {
|
|
525
|
+
shell,
|
|
526
|
+
loading,
|
|
527
|
+
campaign,
|
|
528
|
+
draft,
|
|
529
|
+
step,
|
|
530
|
+
dirty,
|
|
531
|
+
lastSavedAt,
|
|
532
|
+
submitting,
|
|
533
|
+
issues,
|
|
534
|
+
attempted,
|
|
535
|
+
stepStatus,
|
|
536
|
+
fieldErrors,
|
|
537
|
+
selectedSegment,
|
|
538
|
+
estimatedRecipients,
|
|
539
|
+
peekSegment,
|
|
540
|
+
goToStep,
|
|
541
|
+
goBack,
|
|
542
|
+
goNext,
|
|
543
|
+
saveDraft,
|
|
544
|
+
sendTest,
|
|
545
|
+
submit,
|
|
546
|
+
discardDraft,
|
|
547
|
+
};
|
|
548
|
+
provide(key, ctx);
|
|
549
|
+
return ctx;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
export function useCampaignWizard(): CampaignWizardContext {
|
|
553
|
+
const ctx = inject(key);
|
|
554
|
+
if (!ctx)
|
|
555
|
+
throw new Error('useCampaignWizard() called outside CampaignWizard');
|
|
556
|
+
return ctx;
|
|
557
|
+
}
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
### Index.vue — the assembly
|
|
561
|
+
|
|
562
|
+
```vue
|
|
563
|
+
<!-- CampaignWizard/Index.vue -->
|
|
564
|
+
<template>
|
|
565
|
+
<PageShell>
|
|
566
|
+
<!-- Topbar: breadcrumbs left; save state + the destructive escape right.
|
|
567
|
+
Wizard archetype: no page search, no filter rail, no tabs. -->
|
|
568
|
+
<template #topbar>
|
|
569
|
+
<div class="flex items-center justify-between gap-4 border-b px-4 py-2">
|
|
570
|
+
<!-- Breadcrumbs take the row's slack on a stable basis (min-w-0 flex-auto);
|
|
571
|
+
without it their overflow recompute fights the flex layout every frame. -->
|
|
572
|
+
<BbBreadcrumbs class="min-w-0 flex-auto" :items="crumbs" />
|
|
573
|
+
<div class="flex shrink-0 items-center gap-3">
|
|
574
|
+
<span v-if="savedLabel" class="text-xs text-gray-500">{{
|
|
575
|
+
savedLabel
|
|
576
|
+
}}</span>
|
|
577
|
+
<BbButton
|
|
578
|
+
prepend:icon="lucide:trash-2"
|
|
579
|
+
variant="destructive"
|
|
580
|
+
@click="ctx.discardDraft"
|
|
581
|
+
>
|
|
582
|
+
Discard draft
|
|
583
|
+
</BbButton>
|
|
584
|
+
</div>
|
|
585
|
+
</div>
|
|
586
|
+
</template>
|
|
587
|
+
|
|
588
|
+
<div class="mx-auto flex w-full max-w-3xl flex-col gap-5 p-4">
|
|
589
|
+
<header class="flex flex-col gap-0.5">
|
|
590
|
+
<h1 class="text-lg font-semibold">
|
|
591
|
+
{{ ctx.campaign.value?.name ?? 'New campaign' }}
|
|
592
|
+
</h1>
|
|
593
|
+
<p class="text-sm text-gray-500">
|
|
594
|
+
Set up the audience, content and schedule, then review and send.
|
|
595
|
+
</p>
|
|
596
|
+
</header>
|
|
597
|
+
|
|
598
|
+
<StepperHeader />
|
|
599
|
+
|
|
600
|
+
<!-- One wrapper around the alert AND the step outlet: the step swap
|
|
601
|
+
and the validation summary appearing/shrinking both glide. -->
|
|
602
|
+
<!-- Enable height motion only after mount so first paint does not
|
|
603
|
+
animate in; later step swaps still glide. -->
|
|
604
|
+
<BbSmoothHeight :disabled="!stepHeightReady">
|
|
605
|
+
<div class="flex flex-col gap-4">
|
|
606
|
+
<BbAlert
|
|
607
|
+
v-if="summaryIssues.length"
|
|
608
|
+
hide-close
|
|
609
|
+
icon="lucide:circle-x"
|
|
610
|
+
:title="`Fix ${summaryIssues.length} ${summaryIssues.length === 1 ? 'issue' : 'issues'} to continue`"
|
|
611
|
+
variant="destructive"
|
|
612
|
+
>
|
|
613
|
+
<template #text>
|
|
614
|
+
<ul class="list-disc pl-4">
|
|
615
|
+
<li v-for="issue in summaryIssues" :key="issue.field">
|
|
616
|
+
{{ issue.message }}
|
|
617
|
+
</li>
|
|
618
|
+
</ul>
|
|
619
|
+
</template>
|
|
620
|
+
</BbAlert>
|
|
621
|
+
|
|
622
|
+
<div v-if="ctx.loading.value" class="flex justify-center p-10">
|
|
623
|
+
<BbSpinner />
|
|
624
|
+
</div>
|
|
625
|
+
<AudienceStep v-else-if="ctx.step.value === 'audience'" />
|
|
626
|
+
<ContentStep v-else-if="ctx.step.value === 'content'" />
|
|
627
|
+
<ScheduleStep v-else-if="ctx.step.value === 'schedule'" />
|
|
628
|
+
<ReviewStep v-else />
|
|
629
|
+
</div>
|
|
630
|
+
</BbSmoothHeight>
|
|
631
|
+
|
|
632
|
+
<WizardFooter />
|
|
633
|
+
</div>
|
|
634
|
+
|
|
635
|
+
<!-- Clicking the segment's NAME peeks it — Audience step and Review. -->
|
|
636
|
+
<template #sidebar>
|
|
637
|
+
<SegmentPeek
|
|
638
|
+
v-if="ctx.shell.current.value?.type === 'segment'"
|
|
639
|
+
:segment-id="ctx.shell.current.value.id"
|
|
640
|
+
/>
|
|
641
|
+
</template>
|
|
642
|
+
</PageShell>
|
|
643
|
+
</template>
|
|
644
|
+
|
|
645
|
+
<script setup lang="ts">
|
|
646
|
+
import { computed, onMounted, ref } from 'vue';
|
|
647
|
+
import {
|
|
648
|
+
BbAlert,
|
|
649
|
+
BbBreadcrumbs,
|
|
650
|
+
BbButton,
|
|
651
|
+
BbSmoothHeight,
|
|
652
|
+
BbSpinner,
|
|
653
|
+
} from 'bitboss-ui';
|
|
654
|
+
import PageShell from '@/components/layout/PageShell.vue';
|
|
655
|
+
import { provideCampaignWizard } from './context';
|
|
656
|
+
import AudienceStep from './AudienceStep.vue';
|
|
657
|
+
import ContentStep from './ContentStep.vue';
|
|
658
|
+
import ReviewStep from './ReviewStep.vue';
|
|
659
|
+
import ScheduleStep from './ScheduleStep.vue';
|
|
660
|
+
import SegmentPeek from './SegmentPeek.vue';
|
|
661
|
+
import StepperHeader from './StepperHeader.vue';
|
|
662
|
+
import WizardFooter from './WizardFooter.vue';
|
|
663
|
+
|
|
664
|
+
const ctx = provideCampaignWizard();
|
|
665
|
+
|
|
666
|
+
/** Keep SmoothHeight off while the first paint settles (initial load swaps
|
|
667
|
+
* spinner → step content); enable after 300ms for step-to-step glides. */
|
|
668
|
+
const stepHeightReady = ref(false);
|
|
669
|
+
onMounted(() => {
|
|
670
|
+
setTimeout(() => {
|
|
671
|
+
stepHeightReady.value = true;
|
|
672
|
+
}, 300);
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
const crumbs = computed(() => [
|
|
676
|
+
{ key: 'dashboard', text: 'Dashboard', to: '/dashboard' },
|
|
677
|
+
{ key: 'campaigns', text: 'Campaigns', to: '/campaigns' },
|
|
678
|
+
{ key: 'setup', text: ctx.campaign.value?.name ?? 'New campaign' },
|
|
679
|
+
]);
|
|
680
|
+
|
|
681
|
+
// The summary shows only the CURRENT step's issues, and only once the user
|
|
682
|
+
// tried to leave it — it shrinks live as each problem is fixed.
|
|
683
|
+
const summaryIssues = computed(() =>
|
|
684
|
+
ctx.attempted[ctx.step.value] ? ctx.issues.value[ctx.step.value] : []
|
|
685
|
+
);
|
|
686
|
+
|
|
687
|
+
const savedLabel = computed(() => {
|
|
688
|
+
if (ctx.dirty.value) return 'Unsaved changes';
|
|
689
|
+
if (!ctx.lastSavedAt.value) return null;
|
|
690
|
+
const time = new Date(ctx.lastSavedAt.value).toLocaleTimeString('en-US', {
|
|
691
|
+
hour: 'numeric',
|
|
692
|
+
minute: '2-digit',
|
|
693
|
+
});
|
|
694
|
+
return `Draft saved at ${time}`;
|
|
695
|
+
});
|
|
696
|
+
</script>
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
### StepperHeader.vue
|
|
700
|
+
|
|
701
|
+
```vue
|
|
702
|
+
<!-- CampaignWizard/StepperHeader.vue -->
|
|
703
|
+
<template>
|
|
704
|
+
<nav aria-label="Campaign setup steps" class="flex items-center gap-1">
|
|
705
|
+
<template v-for="(stepMeta, index) in STEPS" :key="stepMeta.key">
|
|
706
|
+
<!-- Connector line between steps. -->
|
|
707
|
+
<div
|
|
708
|
+
v-if="index > 0"
|
|
709
|
+
aria-hidden="true"
|
|
710
|
+
class="h-px min-w-3 flex-1 bg-[var(--bb-border)]"
|
|
711
|
+
/>
|
|
712
|
+
|
|
713
|
+
<!-- The primitive gives element semantics + disabled handling; the
|
|
714
|
+
look is entirely ours. Done and upcoming steps jump, locked
|
|
715
|
+
steps are inert. -->
|
|
716
|
+
<BbBaseButton
|
|
717
|
+
:aria-current="status(stepMeta.key) === 'current' ? 'step' : undefined"
|
|
718
|
+
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)]"
|
|
719
|
+
:class="{
|
|
720
|
+
'bg-[var(--bb-hover)] font-medium':
|
|
721
|
+
status(stepMeta.key) === 'current',
|
|
722
|
+
'hover:bg-[var(--bb-hover)]': ['done', 'upcoming'].includes(
|
|
723
|
+
status(stepMeta.key)
|
|
724
|
+
),
|
|
725
|
+
'text-gray-400': status(stepMeta.key) === 'locked',
|
|
726
|
+
}"
|
|
727
|
+
:disabled="status(stepMeta.key) === 'locked'"
|
|
728
|
+
@click="() => ctx.goToStep(stepMeta.key)"
|
|
729
|
+
>
|
|
730
|
+
<span
|
|
731
|
+
class="flex size-5 shrink-0 items-center justify-center rounded-full border text-xs"
|
|
732
|
+
:class="{
|
|
733
|
+
'border-transparent bg-[var(--bb-primary)] text-[var(--bb-primary-fg)]':
|
|
734
|
+
status(stepMeta.key) === 'done',
|
|
735
|
+
'border-[var(--bb-primary)] text-[color:var(--bb-primary)]':
|
|
736
|
+
status(stepMeta.key) === 'current',
|
|
737
|
+
'border-[var(--bb-border)]': status(stepMeta.key) === 'locked',
|
|
738
|
+
}"
|
|
739
|
+
>
|
|
740
|
+
<BbIcon
|
|
741
|
+
v-if="status(stepMeta.key) === 'done'"
|
|
742
|
+
icon="lucide:check"
|
|
743
|
+
size="12"
|
|
744
|
+
/>
|
|
745
|
+
<template v-else>{{ index + 1 }}</template>
|
|
746
|
+
</span>
|
|
747
|
+
{{ stepMeta.label }}
|
|
748
|
+
</BbBaseButton>
|
|
749
|
+
</template>
|
|
750
|
+
</nav>
|
|
751
|
+
</template>
|
|
752
|
+
|
|
753
|
+
<script setup lang="ts">
|
|
754
|
+
import { BbBaseButton, BbIcon } from 'bitboss-ui';
|
|
755
|
+
import { STEPS, useCampaignWizard, type StepKey } from './context';
|
|
756
|
+
|
|
757
|
+
const ctx = useCampaignWizard();
|
|
758
|
+
|
|
759
|
+
const status = (key: StepKey) => ctx.stepStatus(key);
|
|
760
|
+
</script>
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
### AudienceStep.vue
|
|
764
|
+
|
|
765
|
+
```vue
|
|
766
|
+
<!-- CampaignWizard/AudienceStep.vue -->
|
|
767
|
+
<template>
|
|
768
|
+
<section class="flex flex-col gap-4">
|
|
769
|
+
<div class="flex flex-col gap-0.5">
|
|
770
|
+
<h2 class="text-base font-semibold">Who gets this email?</h2>
|
|
771
|
+
<p class="text-sm text-gray-500">
|
|
772
|
+
Pick a segment, then narrow it with delivery filters.
|
|
773
|
+
</p>
|
|
774
|
+
</div>
|
|
775
|
+
|
|
776
|
+
<!-- Query-aware items function: the select searches the API as you type. -->
|
|
777
|
+
<BbSelect
|
|
778
|
+
v-model="ctx.draft.segmentId"
|
|
779
|
+
:errors="segmentErrors"
|
|
780
|
+
item-text="name"
|
|
781
|
+
item-value="id"
|
|
782
|
+
:items="searchSegments"
|
|
783
|
+
label="Segment"
|
|
784
|
+
label-mode="outside"
|
|
785
|
+
placeholder="Search segments…"
|
|
786
|
+
required
|
|
787
|
+
/>
|
|
788
|
+
|
|
789
|
+
<!-- Resolved segment + live estimate: the context owns both fetches. -->
|
|
790
|
+
<div
|
|
791
|
+
v-if="ctx.selectedSegment.value"
|
|
792
|
+
class="flex flex-col gap-1 rounded-[var(--bb-radius)] border p-3 text-sm"
|
|
793
|
+
>
|
|
794
|
+
<div class="flex items-center justify-between gap-2">
|
|
795
|
+
<BbButton size="sm" variant="link" @click="ctx.peekSegment">
|
|
796
|
+
{{ ctx.selectedSegment.value.name }}
|
|
797
|
+
</BbButton>
|
|
798
|
+
<span class="text-xs text-gray-500">
|
|
799
|
+
Last synced {{ shortDate(ctx.selectedSegment.value.lastSyncedAt) }}
|
|
800
|
+
</span>
|
|
801
|
+
</div>
|
|
802
|
+
<p class="text-gray-500">
|
|
803
|
+
{{ number.format(ctx.selectedSegment.value.contactCount) }} contacts in
|
|
804
|
+
the segment ·
|
|
805
|
+
<strong class="text-[var(--bb-text)]">
|
|
806
|
+
{{
|
|
807
|
+
ctx.estimatedRecipients.value === null
|
|
808
|
+
? '…'
|
|
809
|
+
: number.format(ctx.estimatedRecipients.value)
|
|
810
|
+
}}
|
|
811
|
+
</strong>
|
|
812
|
+
estimated recipients after filters
|
|
813
|
+
</p>
|
|
814
|
+
</div>
|
|
815
|
+
|
|
816
|
+
<BbSelect
|
|
817
|
+
v-model="ctx.draft.countries"
|
|
818
|
+
:items="COUNTRIES"
|
|
819
|
+
item-text="label"
|
|
820
|
+
item-value="value"
|
|
821
|
+
label="Limit to countries"
|
|
822
|
+
label-mode="outside"
|
|
823
|
+
multiple
|
|
824
|
+
placeholder="All countries"
|
|
825
|
+
/>
|
|
826
|
+
|
|
827
|
+
<BbSwitch
|
|
828
|
+
v-model="ctx.draft.excludeRecent"
|
|
829
|
+
description="Avoids fatigue: anyone who already received a campaign this week is skipped."
|
|
830
|
+
label="Skip contacts emailed in the last 7 days"
|
|
831
|
+
/>
|
|
832
|
+
</section>
|
|
833
|
+
</template>
|
|
834
|
+
|
|
835
|
+
<script setup lang="ts">
|
|
836
|
+
import { computed } from 'vue';
|
|
837
|
+
import { BbButton, BbSelect, BbSwitch } from 'bitboss-ui';
|
|
838
|
+
import { COUNTRIES, useCampaignWizard } from './context';
|
|
839
|
+
import { api } from '@/api';
|
|
840
|
+
import type { Segment } from '@/types';
|
|
841
|
+
|
|
842
|
+
const ctx = useCampaignWizard();
|
|
843
|
+
|
|
844
|
+
const searchSegments = (query: string): Promise<Segment[]> =>
|
|
845
|
+
api.segments.list({ search: query });
|
|
846
|
+
|
|
847
|
+
// Inline errors appear only after a blocked Next (attempted), per the context.
|
|
848
|
+
const segmentErrors = computed(() => ctx.fieldErrors('audience', 'segmentId'));
|
|
849
|
+
|
|
850
|
+
const number = new Intl.NumberFormat('en-US');
|
|
851
|
+
const shortDate = (value: string) =>
|
|
852
|
+
new Date(value).toLocaleDateString('en-US', {
|
|
853
|
+
month: 'short',
|
|
854
|
+
day: 'numeric',
|
|
855
|
+
});
|
|
856
|
+
</script>
|
|
857
|
+
```
|
|
858
|
+
|
|
859
|
+
### ContentStep.vue
|
|
860
|
+
|
|
861
|
+
```vue
|
|
862
|
+
<!-- CampaignWizard/ContentStep.vue -->
|
|
863
|
+
<template>
|
|
864
|
+
<section class="flex flex-col gap-4">
|
|
865
|
+
<div class="flex flex-col gap-0.5">
|
|
866
|
+
<h2 class="text-base font-semibold">What does it say?</h2>
|
|
867
|
+
<p class="text-sm text-gray-500">
|
|
868
|
+
Subject and preheader are what people see in the inbox list.
|
|
869
|
+
</p>
|
|
870
|
+
</div>
|
|
871
|
+
|
|
872
|
+
<BbTextInput
|
|
873
|
+
v-model="ctx.draft.subject"
|
|
874
|
+
:description="`${ctx.draft.subject.length}/150 characters`"
|
|
875
|
+
:errors="errorsFor('subject')"
|
|
876
|
+
label="Subject line"
|
|
877
|
+
label-mode="outside"
|
|
878
|
+
placeholder="The July roast drop is here — 20% off subscriptions"
|
|
879
|
+
required
|
|
880
|
+
/>
|
|
881
|
+
|
|
882
|
+
<BbTextInput
|
|
883
|
+
v-model="ctx.draft.preheader"
|
|
884
|
+
description="Optional. Shows after the subject in most inboxes — keep it under 90 characters."
|
|
885
|
+
label="Preheader"
|
|
886
|
+
label-mode="outside"
|
|
887
|
+
placeholder="Three new single origins, roasted this week."
|
|
888
|
+
/>
|
|
889
|
+
|
|
890
|
+
<!-- Fixed rows on purpose: an auto-growing textarea inside BbSmoothHeight
|
|
891
|
+
would re-animate the outlet on every line break while typing. -->
|
|
892
|
+
<BbTextarea
|
|
893
|
+
v-model="ctx.draft.body"
|
|
894
|
+
:errors="errorsFor('body')"
|
|
895
|
+
label="Body"
|
|
896
|
+
label-mode="outside"
|
|
897
|
+
placeholder="Write the email…"
|
|
898
|
+
required
|
|
899
|
+
:rows="10"
|
|
900
|
+
/>
|
|
901
|
+
|
|
902
|
+
<BbRadioGroup
|
|
903
|
+
v-model="ctx.draft.templateId"
|
|
904
|
+
:errors="errorsFor('templateId')"
|
|
905
|
+
item-text="name"
|
|
906
|
+
item-value="id"
|
|
907
|
+
:items="TEMPLATES"
|
|
908
|
+
legend="Template"
|
|
909
|
+
name="template"
|
|
910
|
+
required
|
|
911
|
+
>
|
|
912
|
+
<!-- Name + one-line description per option, from the same items. -->
|
|
913
|
+
<template #label="{ item, text }">
|
|
914
|
+
<span class="flex flex-col">
|
|
915
|
+
<span>{{ text }}</span>
|
|
916
|
+
<span class="text-xs text-gray-500">{{ item.description }}</span>
|
|
917
|
+
</span>
|
|
918
|
+
</template>
|
|
919
|
+
</BbRadioGroup>
|
|
920
|
+
|
|
921
|
+
<!-- Auto-loading: the button tracks the async handler by itself. -->
|
|
922
|
+
<div>
|
|
923
|
+
<BbButton
|
|
924
|
+
prepend:icon="lucide:send"
|
|
925
|
+
variant="ghost"
|
|
926
|
+
@click="ctx.sendTest"
|
|
927
|
+
>
|
|
928
|
+
Send test email
|
|
929
|
+
</BbButton>
|
|
930
|
+
</div>
|
|
931
|
+
</section>
|
|
932
|
+
</template>
|
|
933
|
+
|
|
934
|
+
<script setup lang="ts">
|
|
935
|
+
import { BbButton, BbRadioGroup, BbTextarea, BbTextInput } from 'bitboss-ui';
|
|
936
|
+
import { TEMPLATES, useCampaignWizard } from './context';
|
|
937
|
+
|
|
938
|
+
const ctx = useCampaignWizard();
|
|
939
|
+
|
|
940
|
+
const errorsFor = (field: string) => ctx.fieldErrors('content', field);
|
|
941
|
+
</script>
|
|
942
|
+
```
|
|
943
|
+
|
|
944
|
+
### ScheduleStep.vue
|
|
945
|
+
|
|
946
|
+
```vue
|
|
947
|
+
<!-- CampaignWizard/ScheduleStep.vue -->
|
|
948
|
+
<template>
|
|
949
|
+
<section class="flex flex-col gap-4">
|
|
950
|
+
<div class="flex flex-col gap-0.5">
|
|
951
|
+
<h2 class="text-base font-semibold">When does it go out?</h2>
|
|
952
|
+
<p class="text-sm text-gray-500">
|
|
953
|
+
Send it as soon as you confirm, or pick a date.
|
|
954
|
+
</p>
|
|
955
|
+
</div>
|
|
956
|
+
|
|
957
|
+
<BbRadioGroup
|
|
958
|
+
v-model="ctx.draft.sendMode"
|
|
959
|
+
item-text="label"
|
|
960
|
+
item-value="value"
|
|
961
|
+
:items="[
|
|
962
|
+
{
|
|
963
|
+
label: 'Send now — goes out as soon as you confirm on Review',
|
|
964
|
+
value: 'now',
|
|
965
|
+
},
|
|
966
|
+
{ label: 'Schedule for later', value: 'scheduled' },
|
|
967
|
+
]"
|
|
968
|
+
legend="When to send"
|
|
969
|
+
name="send-mode"
|
|
970
|
+
/>
|
|
971
|
+
|
|
972
|
+
<!-- No inner BbSmoothHeight: the page-level wrapper's resize observer
|
|
973
|
+
already animates this region appearing and disappearing. -->
|
|
974
|
+
<div
|
|
975
|
+
v-if="ctx.draft.sendMode === 'scheduled'"
|
|
976
|
+
class="grid gap-4 sm:grid-cols-2"
|
|
977
|
+
>
|
|
978
|
+
<BbDatePickerInput
|
|
979
|
+
v-model="ctx.draft.sendAt"
|
|
980
|
+
description="Campaigns go out at 9:00 AM local to the timezone you pick."
|
|
981
|
+
:errors="errorsFor('sendAt')"
|
|
982
|
+
label="Send date"
|
|
983
|
+
label-mode="outside"
|
|
984
|
+
:min="today"
|
|
985
|
+
required
|
|
986
|
+
/>
|
|
987
|
+
<BbSelect
|
|
988
|
+
v-model="ctx.draft.timezone"
|
|
989
|
+
:errors="errorsFor('timezone')"
|
|
990
|
+
item-text="label"
|
|
991
|
+
item-value="value"
|
|
992
|
+
:items="TIMEZONES"
|
|
993
|
+
label="Timezone"
|
|
994
|
+
label-mode="outside"
|
|
995
|
+
required
|
|
996
|
+
/>
|
|
997
|
+
</div>
|
|
998
|
+
</section>
|
|
999
|
+
</template>
|
|
1000
|
+
|
|
1001
|
+
<script setup lang="ts">
|
|
1002
|
+
import { BbDatePickerInput, BbRadioGroup, BbSelect } from 'bitboss-ui';
|
|
1003
|
+
import { TIMEZONES, useCampaignWizard } from './context';
|
|
1004
|
+
|
|
1005
|
+
const ctx = useCampaignWizard();
|
|
1006
|
+
|
|
1007
|
+
const errorsFor = (field: string) => ctx.fieldErrors('schedule', field);
|
|
1008
|
+
|
|
1009
|
+
// BbDatePickerInput's min takes YYYY-MM-DD.
|
|
1010
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
1011
|
+
</script>
|
|
1012
|
+
```
|
|
1013
|
+
|
|
1014
|
+
### ReviewStep.vue
|
|
1015
|
+
|
|
1016
|
+
```vue
|
|
1017
|
+
<!-- CampaignWizard/ReviewStep.vue -->
|
|
1018
|
+
<template>
|
|
1019
|
+
<section class="flex flex-col gap-5">
|
|
1020
|
+
<div class="flex flex-col gap-0.5">
|
|
1021
|
+
<h2 class="text-base font-semibold">Review and send</h2>
|
|
1022
|
+
<p class="text-sm text-gray-500">
|
|
1023
|
+
Everything below is what goes out. Edit any section — your progress is
|
|
1024
|
+
kept.
|
|
1025
|
+
</p>
|
|
1026
|
+
</div>
|
|
1027
|
+
|
|
1028
|
+
<!-- Audience -->
|
|
1029
|
+
<section
|
|
1030
|
+
class="flex flex-col gap-2 rounded-[var(--bb-radius)] border p-3 text-sm"
|
|
1031
|
+
>
|
|
1032
|
+
<div class="flex items-center justify-between gap-2">
|
|
1033
|
+
<h3 class="text-xs font-semibold uppercase tracking-wide text-gray-500">
|
|
1034
|
+
Audience
|
|
1035
|
+
</h3>
|
|
1036
|
+
<BbButton
|
|
1037
|
+
prepend:icon="lucide:pencil"
|
|
1038
|
+
size="sm"
|
|
1039
|
+
variant="ghost"
|
|
1040
|
+
@click="() => ctx.goToStep('audience')"
|
|
1041
|
+
>
|
|
1042
|
+
Edit
|
|
1043
|
+
</BbButton>
|
|
1044
|
+
</div>
|
|
1045
|
+
<dl class="grid grid-cols-[auto_1fr] gap-x-4 gap-y-1.5">
|
|
1046
|
+
<dt class="text-gray-500">Segment</dt>
|
|
1047
|
+
<dd>
|
|
1048
|
+
<BbButton size="sm" variant="link" @click="ctx.peekSegment">
|
|
1049
|
+
{{ ctx.selectedSegment.value?.name ?? '—' }}
|
|
1050
|
+
</BbButton>
|
|
1051
|
+
</dd>
|
|
1052
|
+
<dt class="text-gray-500">Countries</dt>
|
|
1053
|
+
<dd>{{ countryNames || 'All countries' }}</dd>
|
|
1054
|
+
<dt class="text-gray-500">Fatigue filter</dt>
|
|
1055
|
+
<dd>
|
|
1056
|
+
{{
|
|
1057
|
+
ctx.draft.excludeRecent
|
|
1058
|
+
? 'Skipping contacts emailed in the last 7 days'
|
|
1059
|
+
: 'Off'
|
|
1060
|
+
}}
|
|
1061
|
+
</dd>
|
|
1062
|
+
<dt class="text-gray-500">Estimated recipients</dt>
|
|
1063
|
+
<dd class="font-medium">
|
|
1064
|
+
{{
|
|
1065
|
+
ctx.estimatedRecipients.value === null
|
|
1066
|
+
? '—'
|
|
1067
|
+
: number.format(ctx.estimatedRecipients.value)
|
|
1068
|
+
}}
|
|
1069
|
+
</dd>
|
|
1070
|
+
</dl>
|
|
1071
|
+
</section>
|
|
1072
|
+
|
|
1073
|
+
<!-- Content -->
|
|
1074
|
+
<section
|
|
1075
|
+
class="flex flex-col gap-2 rounded-[var(--bb-radius)] border p-3 text-sm"
|
|
1076
|
+
>
|
|
1077
|
+
<div class="flex items-center justify-between gap-2">
|
|
1078
|
+
<h3 class="text-xs font-semibold uppercase tracking-wide text-gray-500">
|
|
1079
|
+
Content
|
|
1080
|
+
</h3>
|
|
1081
|
+
<BbButton
|
|
1082
|
+
prepend:icon="lucide:pencil"
|
|
1083
|
+
size="sm"
|
|
1084
|
+
variant="ghost"
|
|
1085
|
+
@click="() => ctx.goToStep('content')"
|
|
1086
|
+
>
|
|
1087
|
+
Edit
|
|
1088
|
+
</BbButton>
|
|
1089
|
+
</div>
|
|
1090
|
+
<dl class="grid grid-cols-[auto_1fr] gap-x-4 gap-y-1.5">
|
|
1091
|
+
<dt class="text-gray-500">Subject</dt>
|
|
1092
|
+
<dd class="font-medium">{{ ctx.draft.subject }}</dd>
|
|
1093
|
+
<dt class="text-gray-500">Preheader</dt>
|
|
1094
|
+
<dd>{{ ctx.draft.preheader || '—' }}</dd>
|
|
1095
|
+
<dt class="text-gray-500">Template</dt>
|
|
1096
|
+
<dd>{{ templateName }}</dd>
|
|
1097
|
+
<dt class="text-gray-500">Body</dt>
|
|
1098
|
+
<dd class="text-gray-500">{{ bodyExcerpt }}</dd>
|
|
1099
|
+
</dl>
|
|
1100
|
+
</section>
|
|
1101
|
+
|
|
1102
|
+
<!-- Schedule -->
|
|
1103
|
+
<section
|
|
1104
|
+
class="flex flex-col gap-2 rounded-[var(--bb-radius)] border p-3 text-sm"
|
|
1105
|
+
>
|
|
1106
|
+
<div class="flex items-center justify-between gap-2">
|
|
1107
|
+
<h3 class="text-xs font-semibold uppercase tracking-wide text-gray-500">
|
|
1108
|
+
Schedule
|
|
1109
|
+
</h3>
|
|
1110
|
+
<BbButton
|
|
1111
|
+
prepend:icon="lucide:pencil"
|
|
1112
|
+
size="sm"
|
|
1113
|
+
variant="ghost"
|
|
1114
|
+
@click="() => ctx.goToStep('schedule')"
|
|
1115
|
+
>
|
|
1116
|
+
Edit
|
|
1117
|
+
</BbButton>
|
|
1118
|
+
</div>
|
|
1119
|
+
<p>{{ scheduleSummary }}</p>
|
|
1120
|
+
</section>
|
|
1121
|
+
</section>
|
|
1122
|
+
</template>
|
|
1123
|
+
|
|
1124
|
+
<script setup lang="ts">
|
|
1125
|
+
import { computed } from 'vue';
|
|
1126
|
+
import { BbButton } from 'bitboss-ui';
|
|
1127
|
+
import { COUNTRIES, TEMPLATES, TIMEZONES, useCampaignWizard } from './context';
|
|
1128
|
+
|
|
1129
|
+
const ctx = useCampaignWizard();
|
|
1130
|
+
|
|
1131
|
+
const number = new Intl.NumberFormat('en-US');
|
|
1132
|
+
|
|
1133
|
+
const countryNames = computed(() =>
|
|
1134
|
+
ctx.draft.countries
|
|
1135
|
+
.map(
|
|
1136
|
+
(code) =>
|
|
1137
|
+
COUNTRIES.find((country) => country.value === code)?.label ?? code
|
|
1138
|
+
)
|
|
1139
|
+
.join(', ')
|
|
1140
|
+
);
|
|
1141
|
+
|
|
1142
|
+
const templateName = computed(
|
|
1143
|
+
() =>
|
|
1144
|
+
TEMPLATES.find((template) => template.id === ctx.draft.templateId)?.name ??
|
|
1145
|
+
'—'
|
|
1146
|
+
);
|
|
1147
|
+
|
|
1148
|
+
const bodyExcerpt = computed(() =>
|
|
1149
|
+
ctx.draft.body.length > 140
|
|
1150
|
+
? `${ctx.draft.body.slice(0, 140)}…`
|
|
1151
|
+
: ctx.draft.body
|
|
1152
|
+
);
|
|
1153
|
+
|
|
1154
|
+
const scheduleSummary = computed(() => {
|
|
1155
|
+
if (ctx.draft.sendMode === 'now')
|
|
1156
|
+
return 'Sends immediately after you confirm.';
|
|
1157
|
+
if (!ctx.draft.sendAt) return '—';
|
|
1158
|
+
const date = new Date(ctx.draft.sendAt).toLocaleDateString('en-US', {
|
|
1159
|
+
weekday: 'short',
|
|
1160
|
+
month: 'short',
|
|
1161
|
+
day: 'numeric',
|
|
1162
|
+
year: 'numeric',
|
|
1163
|
+
});
|
|
1164
|
+
const zone =
|
|
1165
|
+
TIMEZONES.find((timezone) => timezone.value === ctx.draft.timezone)
|
|
1166
|
+
?.label ?? ctx.draft.timezone;
|
|
1167
|
+
return `Scheduled for ${date} · 9:00 AM · ${zone}`;
|
|
1168
|
+
});
|
|
1169
|
+
</script>
|
|
1170
|
+
```
|
|
1171
|
+
|
|
1172
|
+
### WizardFooter.vue
|
|
1173
|
+
|
|
1174
|
+
```vue
|
|
1175
|
+
<!-- CampaignWizard/WizardFooter.vue -->
|
|
1176
|
+
<template>
|
|
1177
|
+
<footer class="flex items-center justify-between gap-2 border-t pt-4">
|
|
1178
|
+
<!-- Back is always free — never validated, only bounded. -->
|
|
1179
|
+
<BbButton
|
|
1180
|
+
:disabled="isFirst"
|
|
1181
|
+
prepend:icon="lucide:chevron-left"
|
|
1182
|
+
variant="ghost"
|
|
1183
|
+
@click="ctx.goBack"
|
|
1184
|
+
>
|
|
1185
|
+
Back
|
|
1186
|
+
</BbButton>
|
|
1187
|
+
|
|
1188
|
+
<div class="flex items-center gap-2">
|
|
1189
|
+
<!-- On every step, whatever the validation state. -->
|
|
1190
|
+
<BbButton
|
|
1191
|
+
prepend:icon="lucide:save"
|
|
1192
|
+
variant="ghost"
|
|
1193
|
+
@click="ctx.saveDraft"
|
|
1194
|
+
>
|
|
1195
|
+
Save draft
|
|
1196
|
+
</BbButton>
|
|
1197
|
+
|
|
1198
|
+
<BbButton
|
|
1199
|
+
v-if="!isReview"
|
|
1200
|
+
append:icon="lucide:chevron-right"
|
|
1201
|
+
variant="secondary"
|
|
1202
|
+
@click="ctx.goNext"
|
|
1203
|
+
>
|
|
1204
|
+
Next
|
|
1205
|
+
</BbButton>
|
|
1206
|
+
|
|
1207
|
+
<!-- The page's single primary. -->
|
|
1208
|
+
<BbButton
|
|
1209
|
+
v-else
|
|
1210
|
+
:loading="ctx.submitting.value"
|
|
1211
|
+
prepend:icon="lucide:send"
|
|
1212
|
+
variant="primary"
|
|
1213
|
+
@click="ctx.submit"
|
|
1214
|
+
>
|
|
1215
|
+
{{
|
|
1216
|
+
ctx.draft.sendMode === 'now' ? 'Send campaign' : 'Schedule campaign'
|
|
1217
|
+
}}
|
|
1218
|
+
</BbButton>
|
|
1219
|
+
</div>
|
|
1220
|
+
</footer>
|
|
1221
|
+
</template>
|
|
1222
|
+
|
|
1223
|
+
<script setup lang="ts">
|
|
1224
|
+
import { computed } from 'vue';
|
|
1225
|
+
import { BbButton } from 'bitboss-ui';
|
|
1226
|
+
import { STEPS, useCampaignWizard } from './context';
|
|
1227
|
+
|
|
1228
|
+
const ctx = useCampaignWizard();
|
|
1229
|
+
|
|
1230
|
+
const isFirst = computed(() => ctx.step.value === STEPS[0].key);
|
|
1231
|
+
const isReview = computed(() => ctx.step.value === 'review');
|
|
1232
|
+
</script>
|
|
1233
|
+
```
|
|
1234
|
+
|
|
1235
|
+
### SegmentPeek.vue
|
|
1236
|
+
|
|
1237
|
+
```vue
|
|
1238
|
+
<!-- CampaignWizard/SegmentPeek.vue -->
|
|
1239
|
+
<template>
|
|
1240
|
+
<div v-if="segment" class="flex flex-col gap-4 text-sm">
|
|
1241
|
+
<header class="flex flex-col gap-0.5">
|
|
1242
|
+
<h2 class="text-base font-semibold">{{ segment.name }}</h2>
|
|
1243
|
+
<p class="text-xs text-gray-500">
|
|
1244
|
+
{{ number.format(segment.contactCount) }} contacts · last synced
|
|
1245
|
+
{{ longDate(segment.lastSyncedAt) }}
|
|
1246
|
+
</p>
|
|
1247
|
+
</header>
|
|
1248
|
+
|
|
1249
|
+
<!-- Stat strip. -->
|
|
1250
|
+
<div class="grid grid-cols-3 gap-2">
|
|
1251
|
+
<div class="rounded-[var(--bb-radius)] border p-2">
|
|
1252
|
+
<p class="text-xs text-gray-500">Avg. open rate</p>
|
|
1253
|
+
<p class="font-semibold">{{ percent(segment.openRate) }}</p>
|
|
1254
|
+
</div>
|
|
1255
|
+
<div class="rounded-[var(--bb-radius)] border p-2">
|
|
1256
|
+
<p class="text-xs text-gray-500">Avg. click rate</p>
|
|
1257
|
+
<p class="font-semibold">{{ percent(segment.clickRate) }}</p>
|
|
1258
|
+
</div>
|
|
1259
|
+
<div class="rounded-[var(--bb-radius)] border p-2">
|
|
1260
|
+
<p class="text-xs text-gray-500">Unsubscribes / mo</p>
|
|
1261
|
+
<p class="font-semibold">
|
|
1262
|
+
{{ number.format(segment.monthlyUnsubscribes) }}
|
|
1263
|
+
</p>
|
|
1264
|
+
</div>
|
|
1265
|
+
</div>
|
|
1266
|
+
|
|
1267
|
+
<!-- The segment's definition, so "who is in here" is answerable in place. -->
|
|
1268
|
+
<section class="flex flex-col gap-1.5">
|
|
1269
|
+
<h3 class="text-xs font-semibold uppercase tracking-wide text-gray-500">
|
|
1270
|
+
Conditions
|
|
1271
|
+
</h3>
|
|
1272
|
+
<ul class="flex flex-col gap-1">
|
|
1273
|
+
<li v-for="rule in segment.rules" :key="rule" class="flex gap-2">
|
|
1274
|
+
<BbIcon
|
|
1275
|
+
class="mt-0.5 shrink-0 text-gray-400"
|
|
1276
|
+
icon="lucide:filter"
|
|
1277
|
+
size="14"
|
|
1278
|
+
/>
|
|
1279
|
+
<span>{{ rule }}</span>
|
|
1280
|
+
</li>
|
|
1281
|
+
</ul>
|
|
1282
|
+
</section>
|
|
1283
|
+
|
|
1284
|
+
<!-- Recent campaigns to this segment: fatigue check before sending. -->
|
|
1285
|
+
<section class="flex flex-col gap-1.5">
|
|
1286
|
+
<h3 class="text-xs font-semibold uppercase tracking-wide text-gray-500">
|
|
1287
|
+
Recent campaigns
|
|
1288
|
+
</h3>
|
|
1289
|
+
<ul class="flex flex-col">
|
|
1290
|
+
<li
|
|
1291
|
+
v-for="recent in segment.recentCampaigns"
|
|
1292
|
+
:key="recent.id"
|
|
1293
|
+
class="flex items-center justify-between gap-2 border-b py-1.5 last:border-b-0"
|
|
1294
|
+
>
|
|
1295
|
+
<span>{{ recent.name }}</span>
|
|
1296
|
+
<span class="text-xs text-gray-500">{{
|
|
1297
|
+
longDate(recent.sentAt)
|
|
1298
|
+
}}</span>
|
|
1299
|
+
</li>
|
|
1300
|
+
</ul>
|
|
1301
|
+
</section>
|
|
1302
|
+
|
|
1303
|
+
<footer class="mt-2 flex items-center gap-2 border-t pt-3">
|
|
1304
|
+
<BbButton
|
|
1305
|
+
append:icon="lucide:arrow-up-right"
|
|
1306
|
+
size="sm"
|
|
1307
|
+
:to="`/segments/${segment.id}`"
|
|
1308
|
+
variant="ghost"
|
|
1309
|
+
>
|
|
1310
|
+
Open segment
|
|
1311
|
+
</BbButton>
|
|
1312
|
+
</footer>
|
|
1313
|
+
</div>
|
|
1314
|
+
|
|
1315
|
+
<div v-else class="flex justify-center p-8">
|
|
1316
|
+
<BbSpinner />
|
|
1317
|
+
</div>
|
|
1318
|
+
</template>
|
|
1319
|
+
|
|
1320
|
+
<script setup lang="ts">
|
|
1321
|
+
import { ref, watchEffect } from 'vue';
|
|
1322
|
+
import { BbButton, BbIcon, BbSpinner } from 'bitboss-ui';
|
|
1323
|
+
import { api } from '@/api';
|
|
1324
|
+
import type { SegmentDetail } from '@/types';
|
|
1325
|
+
|
|
1326
|
+
const props = defineProps<{ segmentId: number }>();
|
|
1327
|
+
|
|
1328
|
+
const segment = ref<SegmentDetail | null>(null);
|
|
1329
|
+
watchEffect(async () => {
|
|
1330
|
+
segment.value = await api.segments.getDetail(props.segmentId);
|
|
1331
|
+
});
|
|
1332
|
+
|
|
1333
|
+
const number = new Intl.NumberFormat('en-US');
|
|
1334
|
+
const percent = (value: number) => `${(value * 100).toFixed(1)}%`;
|
|
1335
|
+
const longDate = (value: string) =>
|
|
1336
|
+
new Date(value).toLocaleDateString('en-US', {
|
|
1337
|
+
month: 'short',
|
|
1338
|
+
day: 'numeric',
|
|
1339
|
+
year: 'numeric',
|
|
1340
|
+
});
|
|
1341
|
+
</script>
|
|
1342
|
+
```
|
|
1343
|
+
|
|
1344
|
+
## Why it is built this way
|
|
1345
|
+
|
|
1346
|
+
- **The context coopts, leaves consume.** `shell` is joined once in
|
|
1347
|
+
`provideCampaignWizard()`; seven components share the draft, the step
|
|
1348
|
+
machine, validation and every action through one `useCampaignWizard()`
|
|
1349
|
+
call. The only data prop on the page is `SegmentPeek`'s `segment-id`;
|
|
1350
|
+
no component receives a callback.
|
|
1351
|
+
- **One gating predicate rules everything.** `unlocked(step)` — every
|
|
1352
|
+
step before it validates — drives the stepper's `upcoming`/`locked`
|
|
1353
|
+
states, `goToStep`'s guard, and (via `issues[current]`) `goNext`. A
|
|
1354
|
+
stepper jump is therefore exactly as safe as pressing Next the right
|
|
1355
|
+
number of times; the header can never smuggle the user past validation.
|
|
1356
|
+
Only steps already passed count as `done` — a defaulted step (Schedule
|
|
1357
|
+
starts valid on "send now") stays numbered (`upcoming`), not
|
|
1358
|
+
checkmarked, until the user actually moves past it, though it is
|
|
1359
|
+
jumpable straight away: there is genuinely nothing left to do there.
|
|
1360
|
+
- **Live issues, deferred surfacing.** Recomputing `issues` on every
|
|
1361
|
+
keystroke keeps the stepper and the summary truthful, but errors render
|
|
1362
|
+
only for `attempted` steps — a form that opens covered in red before the
|
|
1363
|
+
user typed anything is the classic wizard failure. Once raised, the
|
|
1364
|
+
summary and inline errors shrink live as each fix lands, and the inputs
|
|
1365
|
+
take plain `:errors` arrays (a non-empty `errors` prop already implies
|
|
1366
|
+
the error state — no separate `has-errors` wiring).
|
|
1367
|
+
- **Steps are four components, not one with a `type` prop.** Each step
|
|
1368
|
+
owns its inputs, its formatting helpers, and nothing else; the assembly
|
|
1369
|
+
is a `v-if` chain inside the `BbSmoothHeight` outlet. A single
|
|
1370
|
+
`WizardStep` with a renderer switch is the API-monster seed.
|
|
1371
|
+
- **`BbBaseButton` is the right stepper primitive.** The steps are
|
|
1372
|
+
clickable surfaces whose look is entirely bespoke — token-styled pills
|
|
1373
|
+
with a number/check bullet — and the primitive contributes what CSS
|
|
1374
|
+
can't: a real `<button>`, native `disabled` for locked steps, and a
|
|
1375
|
+
focus scaffold (the visible ring is ours, per its guide). `aria-current="step"`
|
|
1376
|
+
is set manually — the prop-based `ariaCurrentValue` only applies to
|
|
1377
|
+
exact-active _links_.
|
|
1378
|
+
- **One `BbSmoothHeight`, wrapping alert + outlet.** Both height changes
|
|
1379
|
+
on this page — the step swap and the summary toggling — animate through
|
|
1380
|
+
the same wrapper, so they compose into one glide when Next is blocked.
|
|
1381
|
+
Regions that appear _inside_ a step (the scheduled-date grid) are caught
|
|
1382
|
+
by the same resize observer; and the body textarea keeps fixed `rows`
|
|
1383
|
+
because an auto-growing textarea inside the wrapper would re-animate the
|
|
1384
|
+
outlet on every line break. Height motion is enabled only after mount
|
|
1385
|
+
(`:disabled` until a 300ms `onMounted` flag flips), so the first paint
|
|
1386
|
+
never animates in.
|
|
1387
|
+
- **Refresh restores everything** because the two halves of wizard state
|
|
1388
|
+
live in the right places: field values on the server (the list page
|
|
1389
|
+
created the draft record before navigating here, so `saveDraft` is an
|
|
1390
|
+
update, not a create), position in the URL. On load the requested
|
|
1391
|
+
`?step` is clamped to the first incomplete step, so a stale or forged
|
|
1392
|
+
link degrades to the right place instead of an unusable locked screen.
|
|
1393
|
+
- **The guard, submit and discard share one snapshot.** `dirty` is a JSON
|
|
1394
|
+
compare against the last save; `submit` and `discardDraft` reset the
|
|
1395
|
+
snapshot before navigating so the route-leave confirm never re-asks
|
|
1396
|
+
about changes that were just sent or deliberately destroyed.
|
|
1397
|
+
- **Sizing:** everything follows `md`-first — footer, topbar and step
|
|
1398
|
+
forms use default-size buttons and regular inputs; `sm` appears only on
|
|
1399
|
+
the dense inline surfaces (Review's Edit/segment links, the peek
|
|
1400
|
+
footer). One `variant="primary"` (Send/Schedule); Next is `secondary`;
|
|
1401
|
+
Save draft, Back and Send test are ghosts; the two destructive actions
|
|
1402
|
+
(discard, leave-with-changes) both ride `useConfirm`.
|
|
1403
|
+
|
|
1404
|
+
## Variations
|
|
1405
|
+
|
|
1406
|
+
- **Branching steps**: make `STEPS` a computed — e.g. an A/B test
|
|
1407
|
+
campaign inserts a "Variants" step after Content. The status predicate,
|
|
1408
|
+
URL mapping and footer already work off the array, so nothing else
|
|
1409
|
+
changes.
|
|
1410
|
+
- **Autosave**: debounce a watcher on `draft` that calls a silent
|
|
1411
|
+
`saveDraft` (no toast); the footer button and the topbar save-state stay
|
|
1412
|
+
as the manual escape, and the leave guard almost never fires.
|
|
1413
|
+
- **Vertical checklist stepper** (Mailchimp's actual layout): swap
|
|
1414
|
+
`StepperHeader` for a left rail where each step row also shows its
|
|
1415
|
+
one-line summary and the step bodies expand in place — the context and
|
|
1416
|
+
its predicate are unchanged.
|
|
1417
|
+
- **Send time**: add an hour `BbSelect` beside the timezone and fold it
|
|
1418
|
+
into `sendAt` on save, replacing the fixed 9:00 AM policy.
|