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,1806 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Upload Center (Vue)
|
|
3
|
+
summary: Full-anatomy expense-receipts upload center for a vue-router SPA — multi-file dropzone into an inline-editable table (text, category popover, amount, date cells), per-file progress with inline retry, submit-all with count, bulk bottom panel, and a stacked receipt/merchant peek sidebar, all through one coopting page context.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Recipe: Upload Center (Vue)
|
|
7
|
+
|
|
8
|
+
An Expensify-grade expense receipts submission page for a **plain Vue SPA
|
|
9
|
+
with vue-router**, at full standard anatomy: a multi-file dropzone feeding
|
|
10
|
+
an inline-editable table, per-file upload progress with inline retry,
|
|
11
|
+
one-click category cells, bulk actions in the floating bottom panel, and a
|
|
12
|
+
stacked receipt/merchant peek sidebar.
|
|
13
|
+
|
|
14
|
+
Sibling recipes: [Nuxt](../nuxt/upload-center.md) ·
|
|
15
|
+
[Inertia](../inertia/upload-center.md).
|
|
16
|
+
|
|
17
|
+
## Prerequisites
|
|
18
|
+
|
|
19
|
+
- `<BbConfirm />` and `<BbToast />` mounted once in the app root.
|
|
20
|
+
- [App Layout](../../guides/app-layout.md), [Page Shell](../../guides/page-shell.md)
|
|
21
|
+
and [Floating Bottom Panel](../../guides/floating-bottom-panel.md) copied
|
|
22
|
+
into `src/components/layout/` per their guides — every recipe page assumes
|
|
23
|
+
this chrome exists and renders inside it.
|
|
24
|
+
- A `useCategoriesStore()` Pinia store exposing the shared expense-category
|
|
25
|
+
catalog (Travel, Meals & entertainment, Lodging, Rideshare & transit,
|
|
26
|
+
Software & subscriptions, Office supplies, Fuel, Client entertainment).
|
|
27
|
+
- The registered `soft-*` badge variants (`soft-gray` / `soft-amber` /
|
|
28
|
+
`soft-green`) from the [Inline Edit Workspace](./inline-edit-workspace.md)
|
|
29
|
+
recipe — the receipt-status maps in `context.ts` use them
|
|
30
|
+
(design-language § Using color).
|
|
31
|
+
|
|
32
|
+
## Key ideas
|
|
33
|
+
|
|
34
|
+
- **One coopting page context.** `provideUploadCenter()` joins the
|
|
35
|
+
page-shell stack and the table context once, coopts the shared category
|
|
36
|
+
catalog from `useCategoriesStore()`, owns the filters, the tab, and the
|
|
37
|
+
**upload queue**, and exposes complete actions. Every leaf calls
|
|
38
|
+
`useUploadCenter()` and nothing else.
|
|
39
|
+
- **Capture and transfer are two layers.** `BbDropzone` only validates and
|
|
40
|
+
hands over `File[]`; the context runs the uploads (AbortController +
|
|
41
|
+
progress callback per file) and the dropzone leaf immediately drains its
|
|
42
|
+
model into `ctx.enqueueFiles()`. The component never uploads — that is
|
|
43
|
+
its documented contract.
|
|
44
|
+
- **The drafts table lists two kinds of rows.** In-flight/failed uploads
|
|
45
|
+
(`kind: 'upload'`, local state) render above the server drafts
|
|
46
|
+
(`kind: 'receipt'`, fetched). Progress ticks mutate the live row objects
|
|
47
|
+
— no refetch per tick; only membership changes (enqueue, remove,
|
|
48
|
+
completion) bump `refreshKey`.
|
|
49
|
+
- **Every draft cell edits in place.** Merchant is a text cell committing
|
|
50
|
+
on blur/Enter (§6.3), category is a one-click badge popover (§6.1),
|
|
51
|
+
amount is a `$`-prefixed number cell, date commits on pick. Each cell
|
|
52
|
+
type is its own leaf with a single `receipt` prop; failed saves revert
|
|
53
|
+
via refetch and toast destructive.
|
|
54
|
+
- **Tabs are URL filters.** Drafts / Submitted / Reimbursed carry `href`
|
|
55
|
+
(`?tab=drafts`) and live counts from every fetch; the same table is the
|
|
56
|
+
pane, read-only outside Drafts.
|
|
57
|
+
- **Clicking the name of a thing peeks it.** Filename → receipt peek
|
|
58
|
+
(fresh stack) with preview placeholder, extracted OCR fields and
|
|
59
|
+
activity; the merchant name inside the peek → merchant peek **stacked**
|
|
60
|
+
with a back arrow, whose recent receipts drill back down.
|
|
61
|
+
- **Buttons are `md` by default**; `sm`/`xs` only in the table rows, the
|
|
62
|
+
filter pills, the peek footers and the bottom bar. One
|
|
63
|
+
`variant="primary"`: **Submit all** with its live count.
|
|
64
|
+
|
|
65
|
+
- **Spread the filters object into `dependencies`.** `BbTable` hash-compares
|
|
66
|
+
`dependencies`; it does not deep-watch them. Passing the reactive `filters`
|
|
67
|
+
proxy itself tracks only its identity — which never changes — so the table
|
|
68
|
+
never refetches. `{ ...ctx.filters }` reads every field during render, so any
|
|
69
|
+
field change yields a new, differently-hashed array. Read `.value` off the
|
|
70
|
+
refs for the same reason: context members are not auto-unwrapped in a template.
|
|
71
|
+
|
|
72
|
+
## Page anatomy
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
UploadCenter/
|
|
76
|
+
Index.vue ← assembly: PageShell slots, dropzone, tabs, table, pagination
|
|
77
|
+
context.ts ← coopting page context (shell + table + categories + upload queue + actions)
|
|
78
|
+
ReceiptDropzone.vue ← multi-file capture, constraint errors mapped to messages
|
|
79
|
+
FilterRail.vue ← pill filters + clear-all
|
|
80
|
+
ReceiptFileCell.vue ← file icon + name (peek link) + size, both row kinds
|
|
81
|
+
UploadProgressCell.vue ← per-file BbProgress / failed state with inline retry
|
|
82
|
+
MerchantCell.vue ← inline text edit, commit on blur/Enter (§6.3)
|
|
83
|
+
CategoryCell.vue ← one-click badge → BbSelectPopover (§6.1)
|
|
84
|
+
AmountCell.vue ← inline number edit with $ prefix
|
|
85
|
+
DateCell.vue ← date picker cell, commit on pick
|
|
86
|
+
RowActions.vue ← remove-from-queue / per-receipt "…" dropdown
|
|
87
|
+
SelectionBar.vue ← bulk categorize + bulk remove in the floating bottom panel
|
|
88
|
+
ReceiptPeek.vue ← sidebar workbench, stack level 1
|
|
89
|
+
MerchantPeek.vue ← stacked peek, back arrow via the shell
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Chrome assumed from the guides: [App Layout](../../guides/app-layout.md)
|
|
93
|
+
(left nav + fat footer), [Page Shell](../../guides/page-shell.md),
|
|
94
|
+
[Floating Bottom Panel](../../guides/floating-bottom-panel.md).
|
|
95
|
+
|
|
96
|
+
## Action inventory
|
|
97
|
+
|
|
98
|
+
| # | Action | Trigger | Mechanism |
|
|
99
|
+
| --- | --------------------------------- | ---------------------------------- | ----------------------------------------------------- |
|
|
100
|
+
| 1 | Upload receipts | dropzone (drag, click, Browse) | `enqueueFiles()` → per-file upload with progress |
|
|
101
|
+
| 2 | Retry a failed upload | inline in the progress cell | `retryUpload(id)` re-issues the request |
|
|
102
|
+
| 3 | Remove an in-flight upload | row actions (x) | `removeUpload(id)` aborts + drops the row |
|
|
103
|
+
| 4 | Page search | topbar right | `filters.search` → fetch dependency |
|
|
104
|
+
| 5 | Filter category / merchant / date | filter rail pills | `BbSelectPopover` multiple / query-aware / date range |
|
|
105
|
+
| 6 | Clear filters | rail right (only when active) | context `clearFilters()` |
|
|
106
|
+
| 7 | Switch tab | href tabs with counts | URL + fetch dependency |
|
|
107
|
+
| 8 | Edit merchant inline | merchant cell | commit on blur/Enter, Esc reverts (§6.3) |
|
|
108
|
+
| 9 | Change category one-click | category badge in row & peek | `BbSelectPopover` on the badge (§6.1) |
|
|
109
|
+
| 10 | Edit amount inline | amount cell ($ prefix) | commit on blur/Enter, Esc reverts |
|
|
110
|
+
| 11 | Edit expense date inline | date cell | commit on pick |
|
|
111
|
+
| 12 | Submit one receipt | row `…`, peek footer | `submitReceipt()` (API + refresh + toast) |
|
|
112
|
+
| 13 | Submit all with count | topbar, the page's one `primary` | `submitAll()` — count from every fetch |
|
|
113
|
+
| 14 | Remove a draft | row `…`, peek footer | destructive behind `useConfirm` |
|
|
114
|
+
| 15 | Bulk categorize | floating bottom panel popover | `bulkCategorize()` on `table.selected` |
|
|
115
|
+
| 16 | Bulk remove | floating bottom panel | destructive behind `useConfirm`, count in message |
|
|
116
|
+
| 17 | Select rows / select all | table checkboxes (drafts only) | table context, `selectable` predicate |
|
|
117
|
+
| 18 | Peek receipt | filename cell / merchant peek list | `peekReceipt(id)` → shell stack |
|
|
118
|
+
| 19 | Peek merchant (stacked) | merchant name inside the peek | `stackMerchant(name)` → back arrow |
|
|
119
|
+
| 20 | Download original | row `…`, peek footer | navigation (`/receipts/:id/file`) |
|
|
120
|
+
|
|
121
|
+
## The code
|
|
122
|
+
|
|
123
|
+
### context.ts
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
// UploadCenter/context.ts
|
|
127
|
+
import {
|
|
128
|
+
computed,
|
|
129
|
+
inject,
|
|
130
|
+
provide,
|
|
131
|
+
reactive,
|
|
132
|
+
ref,
|
|
133
|
+
type ComputedRef,
|
|
134
|
+
type InjectionKey,
|
|
135
|
+
type Ref,
|
|
136
|
+
} from 'vue';
|
|
137
|
+
import {
|
|
138
|
+
useBbTableContext,
|
|
139
|
+
useConfirm,
|
|
140
|
+
useToast,
|
|
141
|
+
type BadgeVariantType,
|
|
142
|
+
type BbTableContext,
|
|
143
|
+
} from 'bitboss-ui';
|
|
144
|
+
import {
|
|
145
|
+
usePageShellContext,
|
|
146
|
+
type PageShellContext,
|
|
147
|
+
} from '@/components/layout/usePageShellContext';
|
|
148
|
+
import { useCategoriesStore } from '@/stores/categories';
|
|
149
|
+
import { api } from '@/api';
|
|
150
|
+
import type { Category, Receipt, ReceiptStatus } from '@/types';
|
|
151
|
+
|
|
152
|
+
/** What the stacked sidebar can show. Clicking a name pushes one of these. */
|
|
153
|
+
export type PeekItem =
|
|
154
|
+
| { type: 'receipt'; id: string }
|
|
155
|
+
| { type: 'merchant'; name: string };
|
|
156
|
+
|
|
157
|
+
export type UploadCenterTab = 'drafts' | 'submitted' | 'reimbursed';
|
|
158
|
+
|
|
159
|
+
export interface ReceiptFilters {
|
|
160
|
+
search: string;
|
|
161
|
+
categoryIds: string[];
|
|
162
|
+
merchant: string | null;
|
|
163
|
+
/**
|
|
164
|
+
* `[start, end]` as `YYYY-MM-DD`, or `[]` when unset. Range mode requires an
|
|
165
|
+
* array modelValue -- binding `null` throws, so the empty filter is `[]`.
|
|
166
|
+
*/
|
|
167
|
+
spentBetween: string[];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** A dropped file whose transfer is still in flight (or failed). Local-only. */
|
|
171
|
+
export type UploadRow = {
|
|
172
|
+
/** Local id (`upload-…`) — never collides with server receipt ids. */
|
|
173
|
+
id: string;
|
|
174
|
+
kind: 'upload';
|
|
175
|
+
filename: string;
|
|
176
|
+
size: number;
|
|
177
|
+
/** 0–100. Mutated in place per progress tick — reactive, no refetch. */
|
|
178
|
+
progress: number;
|
|
179
|
+
failed: boolean;
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
/** A server receipt as the table renders it. */
|
|
183
|
+
export type ReceiptRow = Receipt & { kind: 'receipt' };
|
|
184
|
+
|
|
185
|
+
/** What the table actually lists on the Drafts tab: uploads first. */
|
|
186
|
+
export type UploadCenterRow = UploadRow | ReceiptRow;
|
|
187
|
+
|
|
188
|
+
/** The only fields the inline cells may change. */
|
|
189
|
+
export type ReceiptPatch = Partial<
|
|
190
|
+
Pick<Receipt, 'merchant' | 'categoryId' | 'amount' | 'date'>
|
|
191
|
+
>;
|
|
192
|
+
|
|
193
|
+
// -- Receipt-status badge maps: one closed set, mapped once for the whole page.
|
|
194
|
+
// The receipt lifecycle (table rows, the receipt peek header, the merchant
|
|
195
|
+
// peek's recent list) is a small closed set — the textbook case for the soft
|
|
196
|
+
// accents (design-language.md § Using color), registered via badge-variants.d.ts.
|
|
197
|
+
// draft = idle/neutral, submitted = pending approval, reimbursed = done.
|
|
198
|
+
export const STATUS_LABEL: Record<ReceiptStatus, string> = {
|
|
199
|
+
draft: 'Draft',
|
|
200
|
+
submitted: 'Submitted',
|
|
201
|
+
reimbursed: 'Reimbursed',
|
|
202
|
+
};
|
|
203
|
+
export const STATUS_VARIANT: Record<ReceiptStatus, BadgeVariantType> = {
|
|
204
|
+
draft: 'soft-gray',
|
|
205
|
+
submitted: 'soft-amber',
|
|
206
|
+
reimbursed: 'soft-green',
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
export interface UploadCenterContext {
|
|
210
|
+
// -- Coopted contexts ------------------------------------------------------
|
|
211
|
+
shell: PageShellContext<PeekItem>;
|
|
212
|
+
table: BbTableContext;
|
|
213
|
+
/** Shared expense-category catalog, coopted from the store. */
|
|
214
|
+
categories: ComputedRef<Category[]>;
|
|
215
|
+
// -- Page-owned state ------------------------------------------------------
|
|
216
|
+
filters: ReceiptFilters;
|
|
217
|
+
hasActiveFilters: ComputedRef<boolean>;
|
|
218
|
+
tab: Ref<UploadCenterTab>;
|
|
219
|
+
tabCounts: Ref<Record<UploadCenterTab, number>>;
|
|
220
|
+
/** Drafts that are complete (merchant + category + amount + date). */
|
|
221
|
+
readyCount: Ref<number>;
|
|
222
|
+
/** The live upload queue — in-flight and failed transfers. */
|
|
223
|
+
uploads: Ref<UploadRow[]>;
|
|
224
|
+
refreshKey: Ref<number>;
|
|
225
|
+
// -- Data ------------------------------------------------------------------
|
|
226
|
+
fetchRows: () => Promise<UploadCenterRow[]>;
|
|
227
|
+
searchMerchants: (query: string) => Promise<string[]>;
|
|
228
|
+
// -- Peek grammar ----------------------------------------------------------
|
|
229
|
+
peekReceipt: (id: string) => void;
|
|
230
|
+
stackReceipt: (id: string) => void;
|
|
231
|
+
stackMerchant: (name: string) => void;
|
|
232
|
+
// -- Actions (complete: API + refresh + toast) ------------------------------
|
|
233
|
+
clearFilters: () => void;
|
|
234
|
+
enqueueFiles: (files: File[]) => void;
|
|
235
|
+
retryUpload: (id: string) => void;
|
|
236
|
+
removeUpload: (id: string) => void;
|
|
237
|
+
updateReceipt: (receipt: Receipt, patch: ReceiptPatch) => Promise<void>;
|
|
238
|
+
submitReceipt: (receipt: Receipt) => Promise<void>;
|
|
239
|
+
submitAll: () => Promise<void>;
|
|
240
|
+
/** Resolves `true` when the receipt was actually deleted (confirm accepted). */
|
|
241
|
+
deleteReceipt: (receipt: Receipt) => Promise<boolean>;
|
|
242
|
+
bulkCategorize: (categoryId: string) => Promise<void>;
|
|
243
|
+
bulkRemove: () => Promise<void>;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const key: InjectionKey<UploadCenterContext> = Symbol('upload-center');
|
|
247
|
+
|
|
248
|
+
export function provideUploadCenter(): UploadCenterContext {
|
|
249
|
+
const { toast } = useToast();
|
|
250
|
+
const { confirm } = useConfirm();
|
|
251
|
+
|
|
252
|
+
// -- Coopt the specific contexts: once, here, for the whole page. ----------
|
|
253
|
+
const shell = usePageShellContext<PeekItem>();
|
|
254
|
+
const table = useBbTableContext('receipts-table', { perPage: 25 });
|
|
255
|
+
const categoriesStore = useCategoriesStore();
|
|
256
|
+
const categories = computed(() => categoriesStore.categories);
|
|
257
|
+
// A shared catalog store does not seed itself just by being read. Kick the
|
|
258
|
+
// load here (idempotent — the store dedupes on an in-flight promise), or the
|
|
259
|
+
// category cells and the bulk-categorize popover stay permanently empty.
|
|
260
|
+
void categoriesStore.load();
|
|
261
|
+
|
|
262
|
+
// -- Page state -------------------------------------------------------------
|
|
263
|
+
const filters = reactive<ReceiptFilters>({
|
|
264
|
+
search: '',
|
|
265
|
+
categoryIds: [],
|
|
266
|
+
merchant: null,
|
|
267
|
+
spentBetween: [],
|
|
268
|
+
});
|
|
269
|
+
const hasActiveFilters = computed(
|
|
270
|
+
() =>
|
|
271
|
+
filters.search !== '' ||
|
|
272
|
+
filters.categoryIds.length > 0 ||
|
|
273
|
+
filters.merchant !== null ||
|
|
274
|
+
filters.spentBetween.length > 0
|
|
275
|
+
);
|
|
276
|
+
const tab = ref<UploadCenterTab>('drafts');
|
|
277
|
+
const tabCounts = ref<Record<UploadCenterTab, number>>({
|
|
278
|
+
drafts: 0,
|
|
279
|
+
submitted: 0,
|
|
280
|
+
reimbursed: 0,
|
|
281
|
+
});
|
|
282
|
+
const readyCount = ref(0);
|
|
283
|
+
const refreshKey = ref(0);
|
|
284
|
+
|
|
285
|
+
const clearFilters = () => {
|
|
286
|
+
filters.search = '';
|
|
287
|
+
filters.categoryIds = [];
|
|
288
|
+
filters.merchant = null;
|
|
289
|
+
filters.spentBetween = [];
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
const refresh = () => {
|
|
293
|
+
refreshKey.value++;
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
// -- Data ---------------------------------------------------------------------
|
|
297
|
+
// The one place the list is fetched. Writes the paging totals into the
|
|
298
|
+
// table context and keeps the tab counts and submit-all count live. A
|
|
299
|
+
// drafts page looks like:
|
|
300
|
+
// { id: 'rcp_3128', filename: 'uber-2026-07-08.pdf', merchant: 'Uber',
|
|
301
|
+
// categoryId: 'cat-rideshare', amount: 42.8, date: '2026-07-08', … }
|
|
302
|
+
// { id: 'rcp_3127', filename: 'IMG_4021.jpg', merchant: 'Starbucks Reserve',
|
|
303
|
+
// categoryId: 'cat-meals', amount: 11.4, date: '2026-07-08', … }
|
|
304
|
+
// { id: 'rcp_3120', filename: 'delta-ticket-0472.pdf', merchant: 'Delta Air Lines',
|
|
305
|
+
// categoryId: 'cat-travel', amount: 428.6, date: '2026-07-06', … }
|
|
306
|
+
// { id: 'rcp_3117', filename: 'marriott-folio.pdf', merchant: 'Marriott Marquis SF',
|
|
307
|
+
// categoryId: 'cat-lodging', amount: 612.33, date: '2026-07-05', … }
|
|
308
|
+
// On the Drafts tab the live upload queue renders above the fetched page.
|
|
309
|
+
const uploads = ref<UploadRow[]>([]);
|
|
310
|
+
|
|
311
|
+
const fetchRows = async (): Promise<UploadCenterRow[]> => {
|
|
312
|
+
const response = await api.receipts.list({
|
|
313
|
+
...filters,
|
|
314
|
+
tab: tab.value,
|
|
315
|
+
page: table.page.value,
|
|
316
|
+
perPage: table.perPage.value,
|
|
317
|
+
});
|
|
318
|
+
table.total.value = response.total;
|
|
319
|
+
tabCounts.value = response.tabCounts;
|
|
320
|
+
readyCount.value = response.readyCount;
|
|
321
|
+
const rows = response.data.map(
|
|
322
|
+
(receipt): ReceiptRow => ({ ...receipt, kind: 'receipt' })
|
|
323
|
+
);
|
|
324
|
+
return tab.value === 'drafts' ? [...uploads.value, ...rows] : rows;
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
const searchMerchants = (query: string): Promise<string[]> =>
|
|
328
|
+
api.merchants.search(query);
|
|
329
|
+
|
|
330
|
+
// -- Upload machinery ----------------------------------------------------------
|
|
331
|
+
// The queue rows are reactive: progress ticks mutate them in place and the
|
|
332
|
+
// rendered cells update without a refetch. Only membership changes
|
|
333
|
+
// (enqueue, remove, completion) bump refreshKey so the table re-runs its
|
|
334
|
+
// items function. Files and abort controllers live in closure maps — they
|
|
335
|
+
// are not render state.
|
|
336
|
+
const uploadFiles = new Map<string, File>();
|
|
337
|
+
const controllers = new Map<string, AbortController>();
|
|
338
|
+
|
|
339
|
+
const startUpload = async (id: string) => {
|
|
340
|
+
const file = uploadFiles.get(id);
|
|
341
|
+
const row = uploads.value.find((upload) => upload.id === id);
|
|
342
|
+
if (!file || !row) return;
|
|
343
|
+
row.failed = false;
|
|
344
|
+
row.progress = 0;
|
|
345
|
+
const controller = new AbortController();
|
|
346
|
+
controllers.set(id, controller);
|
|
347
|
+
try {
|
|
348
|
+
await api.receipts.upload(file, {
|
|
349
|
+
signal: controller.signal,
|
|
350
|
+
onProgress: (percent) => {
|
|
351
|
+
row.progress = percent;
|
|
352
|
+
},
|
|
353
|
+
});
|
|
354
|
+
// Success: the file re-enters as a server draft with OCR-extracted
|
|
355
|
+
// merchant/amount/date prefilled — the refetch swaps the rows.
|
|
356
|
+
uploads.value = uploads.value.filter((upload) => upload.id !== id);
|
|
357
|
+
uploadFiles.delete(id);
|
|
358
|
+
refresh();
|
|
359
|
+
} catch {
|
|
360
|
+
// Keep the row: the progress cell renders the inline Retry.
|
|
361
|
+
row.failed = true;
|
|
362
|
+
} finally {
|
|
363
|
+
controllers.delete(id);
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
const enqueueFiles = (files: File[]) => {
|
|
368
|
+
for (const file of files) {
|
|
369
|
+
const id = `upload-${crypto.randomUUID()}`;
|
|
370
|
+
uploadFiles.set(id, file);
|
|
371
|
+
uploads.value.push({
|
|
372
|
+
id,
|
|
373
|
+
kind: 'upload',
|
|
374
|
+
filename: file.name,
|
|
375
|
+
size: file.size,
|
|
376
|
+
progress: 0,
|
|
377
|
+
failed: false,
|
|
378
|
+
});
|
|
379
|
+
void startUpload(id);
|
|
380
|
+
}
|
|
381
|
+
refresh();
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
const retryUpload = (id: string) => {
|
|
385
|
+
void startUpload(id);
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
const removeUpload = (id: string) => {
|
|
389
|
+
controllers.get(id)?.abort();
|
|
390
|
+
controllers.delete(id);
|
|
391
|
+
uploadFiles.delete(id);
|
|
392
|
+
uploads.value = uploads.value.filter((upload) => upload.id !== id);
|
|
393
|
+
refresh();
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
// -- Peek grammar ----------------------------------------------------------------
|
|
397
|
+
// peekX opens a fresh stack (from the list); stackX drills down (from a peek).
|
|
398
|
+
const peekReceipt = (id: string) => shell.push({ type: 'receipt', id }, true);
|
|
399
|
+
const stackReceipt = (id: string) => shell.push({ type: 'receipt', id });
|
|
400
|
+
const stackMerchant = (name: string) =>
|
|
401
|
+
shell.push({ type: 'merchant', name });
|
|
402
|
+
|
|
403
|
+
// -- Actions --------------------------------------------------------------------
|
|
404
|
+
const updateReceipt = async (receipt: Receipt, patch: ReceiptPatch) => {
|
|
405
|
+
try {
|
|
406
|
+
await api.receipts.update(receipt.id, patch);
|
|
407
|
+
refresh(); // silent on success — the cell already shows the new value
|
|
408
|
+
} catch {
|
|
409
|
+
refresh(); // the refetch reverts the cell to the server value
|
|
410
|
+
toast({
|
|
411
|
+
title: `Could not save ${receipt.filename}. Changes were reverted.`,
|
|
412
|
+
variant: 'destructive',
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
const submitReceipt = async (receipt: Receipt) => {
|
|
418
|
+
await api.receipts.submit(receipt.id);
|
|
419
|
+
refresh();
|
|
420
|
+
toast({
|
|
421
|
+
title: `${receipt.merchant ?? receipt.filename} submitted for approval.`,
|
|
422
|
+
variant: 'success',
|
|
423
|
+
});
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
const submitAll = async () => {
|
|
427
|
+
const { submitted } = await api.receipts.submitDrafts();
|
|
428
|
+
refresh();
|
|
429
|
+
toast({
|
|
430
|
+
title: `${submitted} receipts submitted for approval.`,
|
|
431
|
+
variant: 'success',
|
|
432
|
+
});
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
const deleteReceipt = async (receipt: Receipt): Promise<boolean> => {
|
|
436
|
+
const ok = await confirm({
|
|
437
|
+
variant: 'destructive',
|
|
438
|
+
title: 'Remove receipt',
|
|
439
|
+
text: `Remove ${receipt.filename}? The file is deleted and cannot be recovered.`,
|
|
440
|
+
yes: 'Remove',
|
|
441
|
+
no: 'Cancel',
|
|
442
|
+
});
|
|
443
|
+
if (!ok) return false;
|
|
444
|
+
await api.receipts.delete(receipt.id);
|
|
445
|
+
refresh();
|
|
446
|
+
toast({ title: `${receipt.filename} removed.`, variant: 'success' });
|
|
447
|
+
return true;
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
// Bulk actions read and clear the coopted table context themselves. Only
|
|
451
|
+
// draft receipt rows are selectable, so `selected` holds receipt ids.
|
|
452
|
+
const bulkIds = (): string[] => table.selected.value;
|
|
453
|
+
const clearSelection = () => {
|
|
454
|
+
table.selected.value = [];
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
const bulkCategorize = async (categoryId: string) => {
|
|
458
|
+
const ids = bulkIds();
|
|
459
|
+
await api.receipts.categorizeMany(ids, categoryId);
|
|
460
|
+
const category = categories.value.find((entry) => entry.id === categoryId);
|
|
461
|
+
clearSelection();
|
|
462
|
+
refresh();
|
|
463
|
+
toast({
|
|
464
|
+
title: `${ids.length} receipts filed under ${category?.name ?? 'the selected category'}.`,
|
|
465
|
+
variant: 'success',
|
|
466
|
+
});
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
const bulkRemove = async () => {
|
|
470
|
+
const ids = bulkIds();
|
|
471
|
+
const ok = await confirm({
|
|
472
|
+
variant: 'destructive',
|
|
473
|
+
title: 'Remove receipts',
|
|
474
|
+
text: `Remove ${ids.length} receipts? Their files are deleted and cannot be recovered.`,
|
|
475
|
+
yes: 'Remove',
|
|
476
|
+
no: 'Cancel',
|
|
477
|
+
});
|
|
478
|
+
if (!ok) return;
|
|
479
|
+
await api.receipts.deleteMany(ids);
|
|
480
|
+
clearSelection();
|
|
481
|
+
refresh();
|
|
482
|
+
toast({ title: `${ids.length} receipts removed.`, variant: 'success' });
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
const ctx: UploadCenterContext = {
|
|
486
|
+
shell,
|
|
487
|
+
table,
|
|
488
|
+
categories,
|
|
489
|
+
filters,
|
|
490
|
+
hasActiveFilters,
|
|
491
|
+
tab,
|
|
492
|
+
tabCounts,
|
|
493
|
+
readyCount,
|
|
494
|
+
uploads,
|
|
495
|
+
refreshKey,
|
|
496
|
+
fetchRows,
|
|
497
|
+
searchMerchants,
|
|
498
|
+
peekReceipt,
|
|
499
|
+
stackReceipt,
|
|
500
|
+
stackMerchant,
|
|
501
|
+
clearFilters,
|
|
502
|
+
enqueueFiles,
|
|
503
|
+
retryUpload,
|
|
504
|
+
removeUpload,
|
|
505
|
+
updateReceipt,
|
|
506
|
+
submitReceipt,
|
|
507
|
+
submitAll,
|
|
508
|
+
deleteReceipt,
|
|
509
|
+
bulkCategorize,
|
|
510
|
+
bulkRemove,
|
|
511
|
+
};
|
|
512
|
+
provide(key, ctx);
|
|
513
|
+
return ctx;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
export function useUploadCenter(): UploadCenterContext {
|
|
517
|
+
const ctx = inject(key);
|
|
518
|
+
if (!ctx) throw new Error('useUploadCenter() called outside UploadCenter');
|
|
519
|
+
return ctx;
|
|
520
|
+
}
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
### Index.vue — the assembly
|
|
524
|
+
|
|
525
|
+
```vue
|
|
526
|
+
<!-- UploadCenter/Index.vue -->
|
|
527
|
+
<template>
|
|
528
|
+
<PageShell>
|
|
529
|
+
<!-- Topbar: breadcrumbs left, page search + submit-all right. -->
|
|
530
|
+
<template #topbar>
|
|
531
|
+
<div class="flex items-center justify-between gap-4 border-b px-4 py-2">
|
|
532
|
+
<!-- Breadcrumbs take the row's slack on a stable basis (min-w-0 flex-auto);
|
|
533
|
+
without it their overflow recompute fights the flex layout every frame. -->
|
|
534
|
+
<BbBreadcrumbs
|
|
535
|
+
class="min-w-0 flex-auto"
|
|
536
|
+
:items="[
|
|
537
|
+
{ key: 'dashboard', text: 'Dashboard', to: '/dashboard' },
|
|
538
|
+
{ key: 'expenses', text: 'Expenses', to: '/expenses' },
|
|
539
|
+
{ key: 'receipts', text: 'Receipts' },
|
|
540
|
+
]"
|
|
541
|
+
/>
|
|
542
|
+
<div class="flex shrink-0 items-center gap-2">
|
|
543
|
+
<BbTextInput
|
|
544
|
+
v-model="ctx.filters.search"
|
|
545
|
+
clearable
|
|
546
|
+
compact
|
|
547
|
+
hide-label
|
|
548
|
+
label="Search receipts"
|
|
549
|
+
placeholder="Search merchant or filename…"
|
|
550
|
+
prepend:icon="lucide:search"
|
|
551
|
+
/>
|
|
552
|
+
<!-- The page's single primary: submit-all with a live count.
|
|
553
|
+
BbButton tracks the async handler, so it shows its own
|
|
554
|
+
loading state while the batch submits. -->
|
|
555
|
+
<BbButton
|
|
556
|
+
:disabled="ctx.readyCount.value === 0"
|
|
557
|
+
prepend:icon="lucide:send"
|
|
558
|
+
variant="primary"
|
|
559
|
+
@click="ctx.submitAll"
|
|
560
|
+
>
|
|
561
|
+
{{ submitLabel }}
|
|
562
|
+
</BbButton>
|
|
563
|
+
</div>
|
|
564
|
+
</div>
|
|
565
|
+
</template>
|
|
566
|
+
|
|
567
|
+
<div class="flex flex-col gap-3 p-4">
|
|
568
|
+
<!-- Capture surface: drafts tab only. -->
|
|
569
|
+
<ReceiptDropzone v-if="ctx.tab.value === 'drafts'" />
|
|
570
|
+
|
|
571
|
+
<FilterRail />
|
|
572
|
+
|
|
573
|
+
<!-- Tabs as URL filters: href updates the query, the key feeds the
|
|
574
|
+
fetch. Counts come from the server on every fetch. -->
|
|
575
|
+
<BbTabs
|
|
576
|
+
v-model="ctx.tab.value"
|
|
577
|
+
compact
|
|
578
|
+
:items="[
|
|
579
|
+
{
|
|
580
|
+
key: 'drafts',
|
|
581
|
+
label: `Drafts · ${ctx.tabCounts.value.drafts}`,
|
|
582
|
+
href: '?tab=drafts',
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
key: 'submitted',
|
|
586
|
+
label: `Submitted · ${ctx.tabCounts.value.submitted}`,
|
|
587
|
+
href: '?tab=submitted',
|
|
588
|
+
},
|
|
589
|
+
{
|
|
590
|
+
key: 'reimbursed',
|
|
591
|
+
label: `Reimbursed · ${ctx.tabCounts.value.reimbursed}`,
|
|
592
|
+
href: '?tab=reimbursed',
|
|
593
|
+
},
|
|
594
|
+
]"
|
|
595
|
+
/>
|
|
596
|
+
|
|
597
|
+
<BbTable
|
|
598
|
+
id="receipts-table"
|
|
599
|
+
actions
|
|
600
|
+
caption="Expense receipts"
|
|
601
|
+
:columns="columns"
|
|
602
|
+
compact
|
|
603
|
+
:dependencies="[
|
|
604
|
+
{ ...ctx.filters },
|
|
605
|
+
ctx.tab.value,
|
|
606
|
+
ctx.refreshKey.value,
|
|
607
|
+
]"
|
|
608
|
+
:deps-debounce-time="300"
|
|
609
|
+
item-value="id"
|
|
610
|
+
:items="ctx.fetchRows"
|
|
611
|
+
loading-text="Loading receipts…"
|
|
612
|
+
:row-class="rowClass"
|
|
613
|
+
:selectable="isDraft"
|
|
614
|
+
>
|
|
615
|
+
<!-- Clicking the NAME of a thing peeks it. -->
|
|
616
|
+
<template #filename="{ item }">
|
|
617
|
+
<ReceiptFileCell :row="item" />
|
|
618
|
+
</template>
|
|
619
|
+
|
|
620
|
+
<!-- Editable cells on draft rows; plain text everywhere else. -->
|
|
621
|
+
<template #merchant="{ item }">
|
|
622
|
+
<MerchantCell v-if="isDraft(item)" :receipt="item" />
|
|
623
|
+
<span v-else-if="item.kind === 'upload'" class="text-gray-400"
|
|
624
|
+
>—</span
|
|
625
|
+
>
|
|
626
|
+
<span v-else>{{ item.merchant ?? '—' }}</span>
|
|
627
|
+
</template>
|
|
628
|
+
|
|
629
|
+
<!-- Slot names are normalized: `categoryId` → `#categoryid`. -->
|
|
630
|
+
<template #categoryid="{ item }">
|
|
631
|
+
<CategoryCell v-if="isDraft(item)" :receipt="item" />
|
|
632
|
+
<span v-else-if="item.kind === 'upload'" class="text-gray-400"
|
|
633
|
+
>—</span
|
|
634
|
+
>
|
|
635
|
+
<span v-else>{{ categoryName(item.categoryId) }}</span>
|
|
636
|
+
</template>
|
|
637
|
+
|
|
638
|
+
<template #amount="{ item }">
|
|
639
|
+
<AmountCell v-if="isDraft(item)" :receipt="item" />
|
|
640
|
+
<span v-else-if="item.kind === 'upload'" class="text-gray-400"
|
|
641
|
+
>—</span
|
|
642
|
+
>
|
|
643
|
+
<span v-else>{{
|
|
644
|
+
item.amount === null ? '—' : currency.format(item.amount)
|
|
645
|
+
}}</span>
|
|
646
|
+
</template>
|
|
647
|
+
|
|
648
|
+
<template #date="{ item }">
|
|
649
|
+
<DateCell v-if="isDraft(item)" :receipt="item" />
|
|
650
|
+
<span v-else-if="item.kind === 'upload'" class="text-gray-400"
|
|
651
|
+
>—</span
|
|
652
|
+
>
|
|
653
|
+
<span v-else>{{ item.date ? shortDate(item.date) : '—' }}</span>
|
|
654
|
+
</template>
|
|
655
|
+
|
|
656
|
+
<!-- Status column doubles as the transfer surface for queue rows. -->
|
|
657
|
+
<template #status="{ item }">
|
|
658
|
+
<UploadProgressCell v-if="item.kind === 'upload'" :upload="item" />
|
|
659
|
+
<BbBadge v-else :variant="STATUS_VARIANT[item.status]">{{
|
|
660
|
+
STATUS_LABEL[item.status]
|
|
661
|
+
}}</BbBadge>
|
|
662
|
+
</template>
|
|
663
|
+
|
|
664
|
+
<template #actions="{ item }">
|
|
665
|
+
<RowActions :row="item" />
|
|
666
|
+
</template>
|
|
667
|
+
|
|
668
|
+
<template #no-data>
|
|
669
|
+
<div class="flex flex-col items-center gap-2 p-8 text-center">
|
|
670
|
+
<BbIcon
|
|
671
|
+
aria-hidden="true"
|
|
672
|
+
class="text-gray-400"
|
|
673
|
+
:icon="
|
|
674
|
+
ctx.hasActiveFilters.value
|
|
675
|
+
? 'lucide:search-x'
|
|
676
|
+
: 'lucide:receipt'
|
|
677
|
+
"
|
|
678
|
+
size="2xl"
|
|
679
|
+
/>
|
|
680
|
+
<p class="text-sm font-semibold">
|
|
681
|
+
{{
|
|
682
|
+
ctx.hasActiveFilters.value
|
|
683
|
+
? 'No matching receipts'
|
|
684
|
+
: ctx.tab.value === 'drafts'
|
|
685
|
+
? 'No drafts yet'
|
|
686
|
+
: 'Nothing in this tab yet'
|
|
687
|
+
}}
|
|
688
|
+
</p>
|
|
689
|
+
<p class="text-xs text-gray-500">
|
|
690
|
+
{{
|
|
691
|
+
ctx.hasActiveFilters.value
|
|
692
|
+
? 'Loosen a filter above, or switch tabs to widen the view.'
|
|
693
|
+
: ctx.tab.value === 'drafts'
|
|
694
|
+
? 'Drop receipts above to get started.'
|
|
695
|
+
: 'Submitted and reimbursed receipts land in their own tabs.'
|
|
696
|
+
}}
|
|
697
|
+
</p>
|
|
698
|
+
<BbButton
|
|
699
|
+
v-if="ctx.hasActiveFilters.value"
|
|
700
|
+
prepend:icon="lucide:x"
|
|
701
|
+
variant="ghost"
|
|
702
|
+
@click="ctx.clearFilters"
|
|
703
|
+
>
|
|
704
|
+
Clear filters
|
|
705
|
+
</BbButton>
|
|
706
|
+
</div>
|
|
707
|
+
</template>
|
|
708
|
+
</BbTable>
|
|
709
|
+
|
|
710
|
+
<!-- Joins the table context by id: no page/totalPages wiring at all. -->
|
|
711
|
+
<BbPagination table-id="receipts-table" />
|
|
712
|
+
</div>
|
|
713
|
+
|
|
714
|
+
<SelectionBar />
|
|
715
|
+
|
|
716
|
+
<!-- Stacked peek sidebar: the shell owns back/close; we render by type. -->
|
|
717
|
+
<template #sidebar>
|
|
718
|
+
<ReceiptPeek
|
|
719
|
+
v-if="ctx.shell.current.value?.type === 'receipt'"
|
|
720
|
+
:receipt-id="ctx.shell.current.value.id"
|
|
721
|
+
/>
|
|
722
|
+
<MerchantPeek
|
|
723
|
+
v-else-if="ctx.shell.current.value?.type === 'merchant'"
|
|
724
|
+
:merchant-name="ctx.shell.current.value.name"
|
|
725
|
+
/>
|
|
726
|
+
</template>
|
|
727
|
+
</PageShell>
|
|
728
|
+
</template>
|
|
729
|
+
|
|
730
|
+
<script setup lang="ts">
|
|
731
|
+
import { computed } from 'vue';
|
|
732
|
+
import {
|
|
733
|
+
BbBadge,
|
|
734
|
+
BbBreadcrumbs,
|
|
735
|
+
BbButton,
|
|
736
|
+
BbIcon,
|
|
737
|
+
BbPagination,
|
|
738
|
+
BbTable,
|
|
739
|
+
BbTabs,
|
|
740
|
+
BbTextInput,
|
|
741
|
+
} from 'bitboss-ui';
|
|
742
|
+
import PageShell from '@/components/layout/PageShell.vue';
|
|
743
|
+
import {
|
|
744
|
+
provideUploadCenter,
|
|
745
|
+
STATUS_LABEL,
|
|
746
|
+
STATUS_VARIANT,
|
|
747
|
+
type ReceiptRow,
|
|
748
|
+
type UploadCenterRow,
|
|
749
|
+
} from './context';
|
|
750
|
+
import AmountCell from './AmountCell.vue';
|
|
751
|
+
import CategoryCell from './CategoryCell.vue';
|
|
752
|
+
import DateCell from './DateCell.vue';
|
|
753
|
+
import FilterRail from './FilterRail.vue';
|
|
754
|
+
import MerchantCell from './MerchantCell.vue';
|
|
755
|
+
import MerchantPeek from './MerchantPeek.vue';
|
|
756
|
+
import ReceiptDropzone from './ReceiptDropzone.vue';
|
|
757
|
+
import ReceiptFileCell from './ReceiptFileCell.vue';
|
|
758
|
+
import ReceiptPeek from './ReceiptPeek.vue';
|
|
759
|
+
import RowActions from './RowActions.vue';
|
|
760
|
+
import SelectionBar from './SelectionBar.vue';
|
|
761
|
+
import UploadProgressCell from './UploadProgressCell.vue';
|
|
762
|
+
|
|
763
|
+
const ctx = provideUploadCenter();
|
|
764
|
+
|
|
765
|
+
const currency = new Intl.NumberFormat('en-US', {
|
|
766
|
+
style: 'currency',
|
|
767
|
+
currency: 'USD',
|
|
768
|
+
});
|
|
769
|
+
const shortDate = (value: string) =>
|
|
770
|
+
new Date(value).toLocaleDateString('en-US', {
|
|
771
|
+
month: 'short',
|
|
772
|
+
day: 'numeric',
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
// Every cell renders through a slot, so columns stay label-only definitions.
|
|
776
|
+
const columns = [
|
|
777
|
+
{ key: 'filename', label: 'Receipt' },
|
|
778
|
+
{ key: 'merchant', label: 'Merchant' },
|
|
779
|
+
{ key: 'categoryId', label: 'Category' },
|
|
780
|
+
{ key: 'amount', label: 'Amount', align: 'right' as const },
|
|
781
|
+
{ key: 'date', label: 'Date' },
|
|
782
|
+
{ key: 'status', label: 'Status' },
|
|
783
|
+
];
|
|
784
|
+
|
|
785
|
+
// Only server-side drafts are editable and selectable; queue rows and
|
|
786
|
+
// submitted/reimbursed receipts are not. The narrow predicate keeps the
|
|
787
|
+
// `v-else` (non-draft receipt) branch a real `ReceiptRow`, not `never`.
|
|
788
|
+
const isDraft = (
|
|
789
|
+
item: UploadCenterRow
|
|
790
|
+
): item is ReceiptRow & { status: 'draft' } =>
|
|
791
|
+
item.kind === 'receipt' && item.status === 'draft';
|
|
792
|
+
|
|
793
|
+
// Failed transfers get a tinted row — public row-class API, no internals.
|
|
794
|
+
const rowClass = (item: UploadCenterRow) =>
|
|
795
|
+
item.kind === 'upload' && item.failed
|
|
796
|
+
? 'bg-red-50 dark:bg-red-950/20'
|
|
797
|
+
: undefined;
|
|
798
|
+
|
|
799
|
+
const categoryName = (categoryId: string | null) =>
|
|
800
|
+
ctx.categories.value.find((category) => category.id === categoryId)?.name ??
|
|
801
|
+
'—';
|
|
802
|
+
|
|
803
|
+
const submitLabel = computed(() =>
|
|
804
|
+
ctx.readyCount.value === 0
|
|
805
|
+
? 'Submit receipts'
|
|
806
|
+
: `Submit ${ctx.readyCount.value} ${ctx.readyCount.value === 1 ? 'receipt' : 'receipts'}`
|
|
807
|
+
);
|
|
808
|
+
</script>
|
|
809
|
+
```
|
|
810
|
+
|
|
811
|
+
### ReceiptDropzone.vue
|
|
812
|
+
|
|
813
|
+
```vue
|
|
814
|
+
<!-- UploadCenter/ReceiptDropzone.vue -->
|
|
815
|
+
<template>
|
|
816
|
+
<BbDropzone
|
|
817
|
+
v-model="pending"
|
|
818
|
+
:accept="ACCEPT"
|
|
819
|
+
:errors="dropError"
|
|
820
|
+
:max-files="20"
|
|
821
|
+
:max-size="MAX_SIZE"
|
|
822
|
+
multiple
|
|
823
|
+
@error="onError"
|
|
824
|
+
>
|
|
825
|
+
<template #default="{ dragging, focused, open }">
|
|
826
|
+
<div
|
|
827
|
+
class="flex flex-col items-center gap-2 rounded-[var(--bb-radius)] border-2 border-dashed p-6 text-center transition-colors"
|
|
828
|
+
:class="[
|
|
829
|
+
dragging ? 'border-[var(--bb-primary)]' : 'border-[var(--bb-border)]',
|
|
830
|
+
focused ? 'ring-2 ring-[var(--bb-primary)] ring-offset-2' : '',
|
|
831
|
+
]"
|
|
832
|
+
>
|
|
833
|
+
<BbIcon class="text-gray-400" icon="lucide:receipt" size="lg" />
|
|
834
|
+
<p class="text-sm font-medium">
|
|
835
|
+
{{ dragging ? 'Drop to add receipts' : 'Drop receipts here' }}
|
|
836
|
+
</p>
|
|
837
|
+
<p class="text-xs text-gray-500">
|
|
838
|
+
JPG, PNG, HEIC or PDF · up to 10 MB each · 20 files per drop
|
|
839
|
+
</p>
|
|
840
|
+
<BbButton
|
|
841
|
+
prepend:icon="lucide:folder-open"
|
|
842
|
+
variant="ghost"
|
|
843
|
+
@click="open"
|
|
844
|
+
>
|
|
845
|
+
Browse files
|
|
846
|
+
</BbButton>
|
|
847
|
+
</div>
|
|
848
|
+
</template>
|
|
849
|
+
</BbDropzone>
|
|
850
|
+
</template>
|
|
851
|
+
|
|
852
|
+
<script setup lang="ts">
|
|
853
|
+
import { ref, watch } from 'vue';
|
|
854
|
+
import { BbButton, BbDropzone, BbIcon, type DropZoneError } from 'bitboss-ui';
|
|
855
|
+
import { useUploadCenter } from './context';
|
|
856
|
+
|
|
857
|
+
// Pair every extension with its MIME type: the MIME rule matches by content
|
|
858
|
+
// type, the .ext rule catches drag-drops where the OS leaves file.type empty.
|
|
859
|
+
const ACCEPT = [
|
|
860
|
+
'image/jpeg',
|
|
861
|
+
'.jpg',
|
|
862
|
+
'.jpeg',
|
|
863
|
+
'image/png',
|
|
864
|
+
'.png',
|
|
865
|
+
'image/heic',
|
|
866
|
+
'.heic',
|
|
867
|
+
'application/pdf',
|
|
868
|
+
'.pdf',
|
|
869
|
+
];
|
|
870
|
+
const MAX_SIZE = 10 * 1024 * 1024; // 10 MB per receipt
|
|
871
|
+
|
|
872
|
+
const ctx = useUploadCenter();
|
|
873
|
+
|
|
874
|
+
// BbDropzone captures and validates; the context uploads. The model is a
|
|
875
|
+
// hand-off buffer: drain every validated batch into the queue, then clear it
|
|
876
|
+
// (which also resets the duplicate check for future corrections).
|
|
877
|
+
const pending = ref<File[]>([]);
|
|
878
|
+
watch(pending, (files) => {
|
|
879
|
+
if (files.length === 0) return;
|
|
880
|
+
dropError.value = undefined;
|
|
881
|
+
ctx.enqueueFiles(files);
|
|
882
|
+
pending.value = [];
|
|
883
|
+
});
|
|
884
|
+
|
|
885
|
+
// The errors display is a round-trip: map each typed reject to a sentence
|
|
886
|
+
// and feed it back through the `errors` prop (rendered in the built-in
|
|
887
|
+
// aria-live region). Batch drops emit one error per file; showing the last
|
|
888
|
+
// message keeps the surface calm.
|
|
889
|
+
const dropError = ref<string | undefined>();
|
|
890
|
+
const onError = (error: DropZoneError) => {
|
|
891
|
+
if (error.type === 'maxSize') {
|
|
892
|
+
dropError.value = `${error.filename} is ${error.formattedSize} — receipts are capped at ${error.formattedMaxSize}.`;
|
|
893
|
+
} else if (error.type === 'accept') {
|
|
894
|
+
dropError.value = `${error.filename} is not a JPG, PNG, HEIC or PDF.`;
|
|
895
|
+
} else if (error.type === 'uniqueness') {
|
|
896
|
+
dropError.value = `${error.filename} is already in this drop.`;
|
|
897
|
+
} else {
|
|
898
|
+
dropError.value = `That drop had ${error.totalFiles} files — add at most 20 at a time.`;
|
|
899
|
+
}
|
|
900
|
+
};
|
|
901
|
+
</script>
|
|
902
|
+
```
|
|
903
|
+
|
|
904
|
+
### FilterRail.vue
|
|
905
|
+
|
|
906
|
+
```vue
|
|
907
|
+
<!-- UploadCenter/FilterRail.vue -->
|
|
908
|
+
<template>
|
|
909
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
910
|
+
<!-- Category: multi-select pill over the coopted catalog. -->
|
|
911
|
+
<BbSelectPopover
|
|
912
|
+
v-model="ctx.filters.categoryIds"
|
|
913
|
+
item-text="name"
|
|
914
|
+
item-value="id"
|
|
915
|
+
:items="ctx.categories.value"
|
|
916
|
+
multiple
|
|
917
|
+
width="240px"
|
|
918
|
+
>
|
|
919
|
+
<template #activator="{ props }">
|
|
920
|
+
<BbButton
|
|
921
|
+
v-bind="props"
|
|
922
|
+
append:icon="lucide:chevron-down"
|
|
923
|
+
size="sm"
|
|
924
|
+
variant="outline"
|
|
925
|
+
>
|
|
926
|
+
Category{{
|
|
927
|
+
ctx.filters.categoryIds.length
|
|
928
|
+
? ` · ${ctx.filters.categoryIds.length}`
|
|
929
|
+
: ''
|
|
930
|
+
}}
|
|
931
|
+
</BbButton>
|
|
932
|
+
</template>
|
|
933
|
+
</BbSelectPopover>
|
|
934
|
+
|
|
935
|
+
<!-- Merchant: query-aware search. Merchant names are their own values,
|
|
936
|
+
so the pill labels itself straight from the filter state. -->
|
|
937
|
+
<BbSelectPopover
|
|
938
|
+
v-model="ctx.filters.merchant"
|
|
939
|
+
:items="ctx.searchMerchants"
|
|
940
|
+
width="240px"
|
|
941
|
+
>
|
|
942
|
+
<template #activator="{ props }">
|
|
943
|
+
<BbButton
|
|
944
|
+
v-bind="props"
|
|
945
|
+
append:icon="lucide:chevron-down"
|
|
946
|
+
size="sm"
|
|
947
|
+
variant="outline"
|
|
948
|
+
>
|
|
949
|
+
{{ ctx.filters.merchant ?? 'Merchant' }}
|
|
950
|
+
</BbButton>
|
|
951
|
+
</template>
|
|
952
|
+
</BbSelectPopover>
|
|
953
|
+
|
|
954
|
+
<!-- Spent-between range: a compact input is 28px — flush with the pills. -->
|
|
955
|
+
<BbDatePickerInput
|
|
956
|
+
v-model="ctx.filters.spentBetween"
|
|
957
|
+
clearable
|
|
958
|
+
compact
|
|
959
|
+
floating
|
|
960
|
+
hide-label
|
|
961
|
+
label="Spent between"
|
|
962
|
+
placeholder="Spent between…"
|
|
963
|
+
range
|
|
964
|
+
/>
|
|
965
|
+
|
|
966
|
+
<!-- Clear-all renders only while something is active. -->
|
|
967
|
+
<BbButton
|
|
968
|
+
v-if="ctx.hasActiveFilters.value"
|
|
969
|
+
class="ml-auto"
|
|
970
|
+
prepend:icon="lucide:x"
|
|
971
|
+
size="sm"
|
|
972
|
+
variant="ghost"
|
|
973
|
+
@click="ctx.clearFilters"
|
|
974
|
+
>
|
|
975
|
+
Clear filters
|
|
976
|
+
</BbButton>
|
|
977
|
+
</div>
|
|
978
|
+
</template>
|
|
979
|
+
|
|
980
|
+
<script setup lang="ts">
|
|
981
|
+
import { BbButton, BbDatePickerInput, BbSelectPopover } from 'bitboss-ui';
|
|
982
|
+
import { useUploadCenter } from './context';
|
|
983
|
+
|
|
984
|
+
const ctx = useUploadCenter();
|
|
985
|
+
</script>
|
|
986
|
+
```
|
|
987
|
+
|
|
988
|
+
### ReceiptFileCell.vue
|
|
989
|
+
|
|
990
|
+
```vue
|
|
991
|
+
<!-- UploadCenter/ReceiptFileCell.vue -->
|
|
992
|
+
<template>
|
|
993
|
+
<div class="flex items-center gap-2">
|
|
994
|
+
<BbIcon class="shrink-0 text-gray-400" :icon="fileIcon" size="16" />
|
|
995
|
+
<div class="flex min-w-0 flex-col">
|
|
996
|
+
<!-- Server receipts peek on click; queue rows are not peekable yet. -->
|
|
997
|
+
<BbButton
|
|
998
|
+
v-if="row.kind === 'receipt'"
|
|
999
|
+
class="justify-start"
|
|
1000
|
+
size="sm"
|
|
1001
|
+
variant="link"
|
|
1002
|
+
@click="() => ctx.peekReceipt(row.id)"
|
|
1003
|
+
>
|
|
1004
|
+
{{ row.filename }}
|
|
1005
|
+
</BbButton>
|
|
1006
|
+
<span v-else class="truncate text-sm">{{ row.filename }}</span>
|
|
1007
|
+
<span class="text-xs text-gray-500">{{ formattedSize }}</span>
|
|
1008
|
+
</div>
|
|
1009
|
+
</div>
|
|
1010
|
+
</template>
|
|
1011
|
+
|
|
1012
|
+
<script setup lang="ts">
|
|
1013
|
+
import { computed } from 'vue';
|
|
1014
|
+
import { BbButton, BbIcon } from 'bitboss-ui';
|
|
1015
|
+
import { useUploadCenter, type UploadCenterRow } from './context';
|
|
1016
|
+
|
|
1017
|
+
const props = defineProps<{ row: UploadCenterRow }>();
|
|
1018
|
+
|
|
1019
|
+
const ctx = useUploadCenter();
|
|
1020
|
+
|
|
1021
|
+
const fileIcon = computed(() =>
|
|
1022
|
+
props.row.filename.toLowerCase().endsWith('.pdf')
|
|
1023
|
+
? 'lucide:file-text'
|
|
1024
|
+
: 'lucide:image'
|
|
1025
|
+
);
|
|
1026
|
+
|
|
1027
|
+
const formattedSize = computed(() => {
|
|
1028
|
+
const bytes =
|
|
1029
|
+
props.row.kind === 'upload' ? props.row.size : props.row.fileSize;
|
|
1030
|
+
return bytes >= 1024 * 1024
|
|
1031
|
+
? `${(bytes / (1024 * 1024)).toFixed(1)} MB`
|
|
1032
|
+
: `${Math.max(1, Math.round(bytes / 1024))} KB`;
|
|
1033
|
+
});
|
|
1034
|
+
</script>
|
|
1035
|
+
```
|
|
1036
|
+
|
|
1037
|
+
### UploadProgressCell.vue
|
|
1038
|
+
|
|
1039
|
+
```vue
|
|
1040
|
+
<!-- UploadCenter/UploadProgressCell.vue -->
|
|
1041
|
+
<template>
|
|
1042
|
+
<!-- In flight: a live bar. `upload.progress` is a reactive field mutated
|
|
1043
|
+
by the context's progress callback — no refetch per tick. -->
|
|
1044
|
+
<div v-if="!upload.failed" class="flex min-w-32 items-center gap-2">
|
|
1045
|
+
<BbProgress
|
|
1046
|
+
:aria-label="`Uploading ${upload.filename}`"
|
|
1047
|
+
class="flex-1"
|
|
1048
|
+
:model-value="upload.progress"
|
|
1049
|
+
/>
|
|
1050
|
+
<span class="text-xs tabular-nums text-gray-500"
|
|
1051
|
+
>{{ upload.progress }}%</span
|
|
1052
|
+
>
|
|
1053
|
+
</div>
|
|
1054
|
+
|
|
1055
|
+
<!-- Failed: say so and retry inline. `xs` is fine here — this is the
|
|
1056
|
+
densest surface on the page. -->
|
|
1057
|
+
<div v-else class="flex items-center gap-2">
|
|
1058
|
+
<span class="text-xs font-medium text-red-600 dark:text-red-400"
|
|
1059
|
+
>Upload failed</span
|
|
1060
|
+
>
|
|
1061
|
+
<BbButton
|
|
1062
|
+
prepend:icon="lucide:rotate-cw"
|
|
1063
|
+
size="xs"
|
|
1064
|
+
variant="outline"
|
|
1065
|
+
@click="() => ctx.retryUpload(upload.id)"
|
|
1066
|
+
>
|
|
1067
|
+
Retry
|
|
1068
|
+
</BbButton>
|
|
1069
|
+
</div>
|
|
1070
|
+
</template>
|
|
1071
|
+
|
|
1072
|
+
<script setup lang="ts">
|
|
1073
|
+
import { BbButton, BbProgress } from 'bitboss-ui';
|
|
1074
|
+
import { useUploadCenter, type UploadRow } from './context';
|
|
1075
|
+
|
|
1076
|
+
defineProps<{ upload: UploadRow }>();
|
|
1077
|
+
|
|
1078
|
+
const ctx = useUploadCenter();
|
|
1079
|
+
</script>
|
|
1080
|
+
```
|
|
1081
|
+
|
|
1082
|
+
### MerchantCell.vue
|
|
1083
|
+
|
|
1084
|
+
```vue
|
|
1085
|
+
<!-- UploadCenter/MerchantCell.vue -->
|
|
1086
|
+
<template>
|
|
1087
|
+
<BbTextInput
|
|
1088
|
+
v-model="draft"
|
|
1089
|
+
compact
|
|
1090
|
+
hide-label
|
|
1091
|
+
label="Merchant"
|
|
1092
|
+
placeholder="Add merchant"
|
|
1093
|
+
@blur="commit"
|
|
1094
|
+
@keydown.enter="commit"
|
|
1095
|
+
@keydown.esc="revert"
|
|
1096
|
+
/>
|
|
1097
|
+
</template>
|
|
1098
|
+
|
|
1099
|
+
<script setup lang="ts">
|
|
1100
|
+
import { ref, watch } from 'vue';
|
|
1101
|
+
import { BbTextInput } from 'bitboss-ui';
|
|
1102
|
+
import { useUploadCenter } from './context';
|
|
1103
|
+
import type { Receipt } from '@/types';
|
|
1104
|
+
|
|
1105
|
+
const props = defineProps<{ receipt: Receipt }>();
|
|
1106
|
+
|
|
1107
|
+
const ctx = useUploadCenter();
|
|
1108
|
+
|
|
1109
|
+
// Inline-edit doctrine (§6.3): a local draft, commit on blur/Enter, revert
|
|
1110
|
+
// on Esc. The server value re-seeds the draft whenever a refetch updates the
|
|
1111
|
+
// row — which is also how a failed save reverts.
|
|
1112
|
+
const draft = ref<string | null>(props.receipt.merchant);
|
|
1113
|
+
watch(
|
|
1114
|
+
() => props.receipt.merchant,
|
|
1115
|
+
(value) => {
|
|
1116
|
+
draft.value = value;
|
|
1117
|
+
}
|
|
1118
|
+
);
|
|
1119
|
+
|
|
1120
|
+
const commit = () => {
|
|
1121
|
+
const merchant = draft.value?.trim() || null;
|
|
1122
|
+
if (merchant === props.receipt.merchant) return;
|
|
1123
|
+
void ctx.updateReceipt(props.receipt, { merchant });
|
|
1124
|
+
};
|
|
1125
|
+
const revert = () => {
|
|
1126
|
+
draft.value = props.receipt.merchant;
|
|
1127
|
+
};
|
|
1128
|
+
</script>
|
|
1129
|
+
```
|
|
1130
|
+
|
|
1131
|
+
### CategoryCell.vue
|
|
1132
|
+
|
|
1133
|
+
```vue
|
|
1134
|
+
<!-- UploadCenter/CategoryCell.vue -->
|
|
1135
|
+
<template>
|
|
1136
|
+
<BbSelectPopover
|
|
1137
|
+
compact
|
|
1138
|
+
disable-writing
|
|
1139
|
+
item-text="name"
|
|
1140
|
+
item-value="id"
|
|
1141
|
+
:items="ctx.categories.value"
|
|
1142
|
+
:model-value="receipt.categoryId"
|
|
1143
|
+
width="220px"
|
|
1144
|
+
@update:model-value="(categoryId) => pick(categoryId as string)"
|
|
1145
|
+
>
|
|
1146
|
+
<template #activator="{ props: activatorProps, shown }">
|
|
1147
|
+
<!-- One-click pattern (§6.1): the badge is the focusable trigger —
|
|
1148
|
+
BbBadgeButton takes over the badge body, the trailing chevron says
|
|
1149
|
+
"this can change", and while the save runs `loading` swaps the
|
|
1150
|
+
spinner into the chevron's place. A category is a label, not a
|
|
1151
|
+
semantic status, so a set category reads neutral (`secondary`);
|
|
1152
|
+
only the unset state stays `outline` so empty cells read as
|
|
1153
|
+
quieter to-dos, not values. -->
|
|
1154
|
+
<BbBadge
|
|
1155
|
+
append:icon="lucide:chevron-down"
|
|
1156
|
+
:class="{ 'category-cell--open': shown }"
|
|
1157
|
+
:loading="saving"
|
|
1158
|
+
:variant="receipt.categoryId ? 'secondary' : 'outline'"
|
|
1159
|
+
>
|
|
1160
|
+
<BbBadgeButton v-bind="activatorProps">{{
|
|
1161
|
+
categoryName
|
|
1162
|
+
}}</BbBadgeButton>
|
|
1163
|
+
</BbBadge>
|
|
1164
|
+
</template>
|
|
1165
|
+
</BbSelectPopover>
|
|
1166
|
+
</template>
|
|
1167
|
+
|
|
1168
|
+
<script setup lang="ts">
|
|
1169
|
+
import { computed, ref } from 'vue';
|
|
1170
|
+
import { BbBadge, BbBadgeButton, BbSelectPopover } from 'bitboss-ui';
|
|
1171
|
+
import { useUploadCenter } from './context';
|
|
1172
|
+
import type { Receipt } from '@/types';
|
|
1173
|
+
|
|
1174
|
+
const props = defineProps<{ receipt: Receipt }>();
|
|
1175
|
+
|
|
1176
|
+
const ctx = useUploadCenter();
|
|
1177
|
+
|
|
1178
|
+
// In-flight flag for the save feedback — rung-1 local state, not context.
|
|
1179
|
+
const saving = ref(false);
|
|
1180
|
+
const pick = async (categoryId: string) => {
|
|
1181
|
+
saving.value = true;
|
|
1182
|
+
try {
|
|
1183
|
+
await ctx.updateReceipt(props.receipt, { categoryId });
|
|
1184
|
+
} finally {
|
|
1185
|
+
saving.value = false;
|
|
1186
|
+
}
|
|
1187
|
+
};
|
|
1188
|
+
|
|
1189
|
+
// The trigger labels itself from the coopted catalog, never from the
|
|
1190
|
+
// popover's lazy `text` slot prop.
|
|
1191
|
+
const categoryName = computed(
|
|
1192
|
+
() =>
|
|
1193
|
+
ctx.categories.value.find(
|
|
1194
|
+
(category) => category.id === props.receipt.categoryId
|
|
1195
|
+
)?.name ?? 'Set category'
|
|
1196
|
+
);
|
|
1197
|
+
</script>
|
|
1198
|
+
|
|
1199
|
+
<style scoped>
|
|
1200
|
+
/* Open = the badge's own hue, deepened, plus its variant ring — the same
|
|
1201
|
+
grammar as every badge-activator cell. */
|
|
1202
|
+
.category-cell--open {
|
|
1203
|
+
--bg: color-mix(in oklab, var(--color) 24%, var(--bb-panel));
|
|
1204
|
+
--border-color: color-mix(in oklab, var(--color) 45%, var(--bb-panel));
|
|
1205
|
+
box-shadow: 0 0 0 var(--bb-ring-size) var(--ring);
|
|
1206
|
+
}
|
|
1207
|
+
</style>
|
|
1208
|
+
```
|
|
1209
|
+
|
|
1210
|
+
### AmountCell.vue
|
|
1211
|
+
|
|
1212
|
+
```vue
|
|
1213
|
+
<!-- UploadCenter/AmountCell.vue -->
|
|
1214
|
+
<template>
|
|
1215
|
+
<BbNumberInput
|
|
1216
|
+
v-model="draft"
|
|
1217
|
+
compact
|
|
1218
|
+
hide-label
|
|
1219
|
+
label="Amount"
|
|
1220
|
+
:max-precision="2"
|
|
1221
|
+
:min="0"
|
|
1222
|
+
placeholder="0.00"
|
|
1223
|
+
@blur="commit"
|
|
1224
|
+
@keydown.enter="commit"
|
|
1225
|
+
@keydown.esc="revert"
|
|
1226
|
+
>
|
|
1227
|
+
<template #prefix>$</template>
|
|
1228
|
+
</BbNumberInput>
|
|
1229
|
+
</template>
|
|
1230
|
+
|
|
1231
|
+
<script setup lang="ts">
|
|
1232
|
+
import { ref, watch } from 'vue';
|
|
1233
|
+
import { BbNumberInput } from 'bitboss-ui';
|
|
1234
|
+
import { useUploadCenter } from './context';
|
|
1235
|
+
import type { Receipt } from '@/types';
|
|
1236
|
+
|
|
1237
|
+
const props = defineProps<{ receipt: Receipt }>();
|
|
1238
|
+
|
|
1239
|
+
const ctx = useUploadCenter();
|
|
1240
|
+
|
|
1241
|
+
// Same commit/revert contract as the merchant cell, on the number model.
|
|
1242
|
+
const draft = ref<number | string | null>(props.receipt.amount);
|
|
1243
|
+
watch(
|
|
1244
|
+
() => props.receipt.amount,
|
|
1245
|
+
(value) => {
|
|
1246
|
+
draft.value = value;
|
|
1247
|
+
}
|
|
1248
|
+
);
|
|
1249
|
+
|
|
1250
|
+
const commit = () => {
|
|
1251
|
+
const amount =
|
|
1252
|
+
draft.value === null || draft.value === '' ? null : Number(draft.value);
|
|
1253
|
+
if (amount === props.receipt.amount) return;
|
|
1254
|
+
void ctx.updateReceipt(props.receipt, { amount });
|
|
1255
|
+
};
|
|
1256
|
+
const revert = () => {
|
|
1257
|
+
draft.value = props.receipt.amount;
|
|
1258
|
+
};
|
|
1259
|
+
</script>
|
|
1260
|
+
```
|
|
1261
|
+
|
|
1262
|
+
### DateCell.vue
|
|
1263
|
+
|
|
1264
|
+
```vue
|
|
1265
|
+
<!-- UploadCenter/DateCell.vue -->
|
|
1266
|
+
<template>
|
|
1267
|
+
<!-- Picking a date IS the commit — no blur dance for calendar input.
|
|
1268
|
+
`floating` keeps the model a plain YYYY-MM-DD string. -->
|
|
1269
|
+
<BbDatePickerInput
|
|
1270
|
+
compact
|
|
1271
|
+
floating
|
|
1272
|
+
hide-label
|
|
1273
|
+
label="Expense date"
|
|
1274
|
+
:model-value="receipt.date"
|
|
1275
|
+
placeholder="YYYY-MM-DD"
|
|
1276
|
+
@update:model-value="commit"
|
|
1277
|
+
/>
|
|
1278
|
+
</template>
|
|
1279
|
+
|
|
1280
|
+
<script setup lang="ts">
|
|
1281
|
+
import { BbDatePickerInput } from 'bitboss-ui';
|
|
1282
|
+
import { useUploadCenter } from './context';
|
|
1283
|
+
import type { Receipt } from '@/types';
|
|
1284
|
+
|
|
1285
|
+
const props = defineProps<{ receipt: Receipt }>();
|
|
1286
|
+
|
|
1287
|
+
const ctx = useUploadCenter();
|
|
1288
|
+
|
|
1289
|
+
const commit = (value: string | string[] | null) => {
|
|
1290
|
+
const date = Array.isArray(value) ? (value[0] ?? null) : value;
|
|
1291
|
+
if (date === props.receipt.date) return;
|
|
1292
|
+
void ctx.updateReceipt(props.receipt, { date });
|
|
1293
|
+
};
|
|
1294
|
+
</script>
|
|
1295
|
+
```
|
|
1296
|
+
|
|
1297
|
+
### RowActions.vue
|
|
1298
|
+
|
|
1299
|
+
```vue
|
|
1300
|
+
<!-- UploadCenter/RowActions.vue -->
|
|
1301
|
+
<template>
|
|
1302
|
+
<!-- Queue rows: one explicit remove (aborts the transfer). -->
|
|
1303
|
+
<BbButton
|
|
1304
|
+
v-if="row.kind === 'upload'"
|
|
1305
|
+
:aria-label="`Remove ${row.filename} from the queue`"
|
|
1306
|
+
icon="lucide:x"
|
|
1307
|
+
size="sm"
|
|
1308
|
+
variant="ghost"
|
|
1309
|
+
@click="() => ctx.removeUpload(row.id)"
|
|
1310
|
+
/>
|
|
1311
|
+
|
|
1312
|
+
<!-- Server receipts: contextual dropdown, entries vary by status. -->
|
|
1313
|
+
<BbDropdown v-else :items="items">
|
|
1314
|
+
<template #activator="{ props }">
|
|
1315
|
+
<BbButton v-bind="props" icon="lucide:ellipsis" size="sm" variant="ghost">
|
|
1316
|
+
Receipt actions
|
|
1317
|
+
</BbButton>
|
|
1318
|
+
</template>
|
|
1319
|
+
</BbDropdown>
|
|
1320
|
+
</template>
|
|
1321
|
+
|
|
1322
|
+
<script setup lang="ts">
|
|
1323
|
+
import { computed } from 'vue';
|
|
1324
|
+
import { BbButton, BbDropdown } from 'bitboss-ui';
|
|
1325
|
+
import { useUploadCenter, type UploadCenterRow } from './context';
|
|
1326
|
+
|
|
1327
|
+
const props = defineProps<{ row: UploadCenterRow }>();
|
|
1328
|
+
|
|
1329
|
+
const ctx = useUploadCenter();
|
|
1330
|
+
|
|
1331
|
+
const items = computed(() => {
|
|
1332
|
+
if (props.row.kind !== 'receipt') return [];
|
|
1333
|
+
const receipt = props.row;
|
|
1334
|
+
const isDraft = receipt.status === 'draft';
|
|
1335
|
+
return [
|
|
1336
|
+
...(isDraft
|
|
1337
|
+
? [
|
|
1338
|
+
{
|
|
1339
|
+
key: 'submit',
|
|
1340
|
+
text: 'Submit for approval',
|
|
1341
|
+
'prepend:icon': 'lucide:send',
|
|
1342
|
+
onClick: () => ctx.submitReceipt(receipt),
|
|
1343
|
+
},
|
|
1344
|
+
]
|
|
1345
|
+
: []),
|
|
1346
|
+
{
|
|
1347
|
+
key: 'download',
|
|
1348
|
+
text: 'Download original',
|
|
1349
|
+
'prepend:icon': 'lucide:download',
|
|
1350
|
+
href: `/receipts/${receipt.id}/file`,
|
|
1351
|
+
},
|
|
1352
|
+
...(isDraft
|
|
1353
|
+
? [
|
|
1354
|
+
{
|
|
1355
|
+
key: 'remove',
|
|
1356
|
+
text: 'Remove',
|
|
1357
|
+
'prepend:icon': 'lucide:trash-2',
|
|
1358
|
+
variant: 'destructive' as const,
|
|
1359
|
+
onClick: () => ctx.deleteReceipt(receipt),
|
|
1360
|
+
},
|
|
1361
|
+
]
|
|
1362
|
+
: []),
|
|
1363
|
+
];
|
|
1364
|
+
});
|
|
1365
|
+
</script>
|
|
1366
|
+
```
|
|
1367
|
+
|
|
1368
|
+
### SelectionBar.vue
|
|
1369
|
+
|
|
1370
|
+
```vue
|
|
1371
|
+
<!-- UploadCenter/SelectionBar.vue -->
|
|
1372
|
+
<template>
|
|
1373
|
+
<FloatingBottomPanel :open="selectedCount > 0">
|
|
1374
|
+
<span class="px-2 text-sm">{{ selectedCount }} selected</span>
|
|
1375
|
+
|
|
1376
|
+
<!-- Bulk categorize: the picker lives right in the bar — a one-shot
|
|
1377
|
+
popover (model stays null) whose selection IS the action. -->
|
|
1378
|
+
<BbSelectPopover
|
|
1379
|
+
item-text="name"
|
|
1380
|
+
item-value="id"
|
|
1381
|
+
:items="ctx.categories.value"
|
|
1382
|
+
:model-value="null"
|
|
1383
|
+
placement="top"
|
|
1384
|
+
width="220px"
|
|
1385
|
+
@update:model-value="
|
|
1386
|
+
(categoryId) => categoryId && ctx.bulkCategorize(categoryId)
|
|
1387
|
+
"
|
|
1388
|
+
>
|
|
1389
|
+
<template #activator="{ props }">
|
|
1390
|
+
<BbButton
|
|
1391
|
+
v-bind="props"
|
|
1392
|
+
prepend:icon="lucide:tag"
|
|
1393
|
+
size="sm"
|
|
1394
|
+
variant="ghost"
|
|
1395
|
+
>
|
|
1396
|
+
Set category
|
|
1397
|
+
</BbButton>
|
|
1398
|
+
</template>
|
|
1399
|
+
</BbSelectPopover>
|
|
1400
|
+
|
|
1401
|
+
<div class="floating-bottom-panel__right flex items-center gap-1">
|
|
1402
|
+
<BbButton size="sm" variant="destructive" @click="ctx.bulkRemove"
|
|
1403
|
+
>Remove</BbButton
|
|
1404
|
+
>
|
|
1405
|
+
<BbButton
|
|
1406
|
+
aria-label="Clear selection"
|
|
1407
|
+
icon="lucide:x"
|
|
1408
|
+
size="sm"
|
|
1409
|
+
variant="ghost"
|
|
1410
|
+
@click="clearSelection"
|
|
1411
|
+
/>
|
|
1412
|
+
</div>
|
|
1413
|
+
</FloatingBottomPanel>
|
|
1414
|
+
</template>
|
|
1415
|
+
|
|
1416
|
+
<script setup lang="ts">
|
|
1417
|
+
import { computed } from 'vue';
|
|
1418
|
+
import { BbButton, BbSelectPopover } from 'bitboss-ui';
|
|
1419
|
+
import FloatingBottomPanel from '@/components/layout/FloatingBottomPanel.vue';
|
|
1420
|
+
import { useUploadCenter } from './context';
|
|
1421
|
+
|
|
1422
|
+
const ctx = useUploadCenter();
|
|
1423
|
+
|
|
1424
|
+
const selectedCount = computed(() => ctx.table.selected.value?.length ?? 0);
|
|
1425
|
+
const clearSelection = () => {
|
|
1426
|
+
ctx.table.selected.value = [];
|
|
1427
|
+
};
|
|
1428
|
+
</script>
|
|
1429
|
+
```
|
|
1430
|
+
|
|
1431
|
+
### ReceiptPeek.vue
|
|
1432
|
+
|
|
1433
|
+
```vue
|
|
1434
|
+
<!-- UploadCenter/ReceiptPeek.vue -->
|
|
1435
|
+
<template>
|
|
1436
|
+
<div v-if="receipt" class="flex flex-col gap-4 text-sm">
|
|
1437
|
+
<!-- Header: merchant identity + amount + status. The merchant name
|
|
1438
|
+
drills DOWN — stacked peek, back arrow via the shell. -->
|
|
1439
|
+
<header class="flex flex-col gap-1.5">
|
|
1440
|
+
<div class="flex items-center justify-between gap-2">
|
|
1441
|
+
<BbButton
|
|
1442
|
+
v-if="receipt.merchant"
|
|
1443
|
+
size="sm"
|
|
1444
|
+
variant="link"
|
|
1445
|
+
@click="() => ctx.stackMerchant(receipt!.merchant!)"
|
|
1446
|
+
>
|
|
1447
|
+
{{ receipt.merchant }}
|
|
1448
|
+
</BbButton>
|
|
1449
|
+
<span v-else class="text-gray-500">No merchant yet</span>
|
|
1450
|
+
<BbBadge variant="outline">{{ receipt.status }}</BbBadge>
|
|
1451
|
+
</div>
|
|
1452
|
+
<p class="text-xl font-semibold">
|
|
1453
|
+
{{
|
|
1454
|
+
receipt.amount === null
|
|
1455
|
+
? 'Amount missing'
|
|
1456
|
+
: currency.format(receipt.amount)
|
|
1457
|
+
}}
|
|
1458
|
+
</p>
|
|
1459
|
+
<p class="text-xs text-gray-500">
|
|
1460
|
+
{{ receipt.filename }} · spent
|
|
1461
|
+
{{ receipt.date ? longDate(receipt.date) : '—' }}
|
|
1462
|
+
</p>
|
|
1463
|
+
</header>
|
|
1464
|
+
|
|
1465
|
+
<!-- Preview placeholder: the real app renders the scanned image here. -->
|
|
1466
|
+
<div
|
|
1467
|
+
class="flex h-44 flex-col items-center justify-center gap-1 rounded-[var(--bb-radius)] border bg-[var(--bb-panel)] text-gray-400"
|
|
1468
|
+
>
|
|
1469
|
+
<BbIcon icon="lucide:receipt" size="lg" />
|
|
1470
|
+
<span class="text-xs">Receipt preview</span>
|
|
1471
|
+
</div>
|
|
1472
|
+
|
|
1473
|
+
<!-- Extracted fields: what OCR read off the scan, with confidence. -->
|
|
1474
|
+
<section class="flex flex-col gap-1.5">
|
|
1475
|
+
<div class="flex items-center justify-between">
|
|
1476
|
+
<h3 class="text-xs font-semibold uppercase tracking-wide text-gray-500">
|
|
1477
|
+
Extracted fields
|
|
1478
|
+
</h3>
|
|
1479
|
+
<BbBadge size="xs" variant="outline">{{
|
|
1480
|
+
`OCR · ${receipt.extracted.confidence}%`
|
|
1481
|
+
}}</BbBadge>
|
|
1482
|
+
</div>
|
|
1483
|
+
<dl class="grid grid-cols-[auto_1fr] gap-x-4 gap-y-1.5">
|
|
1484
|
+
<dt class="text-gray-500">Merchant</dt>
|
|
1485
|
+
<dd>{{ receipt.extracted.merchant ?? '—' }}</dd>
|
|
1486
|
+
<dt class="text-gray-500">Total</dt>
|
|
1487
|
+
<dd>
|
|
1488
|
+
{{
|
|
1489
|
+
receipt.extracted.total === null
|
|
1490
|
+
? '—'
|
|
1491
|
+
: currency.format(receipt.extracted.total)
|
|
1492
|
+
}}
|
|
1493
|
+
</dd>
|
|
1494
|
+
<dt class="text-gray-500">Tax</dt>
|
|
1495
|
+
<dd>
|
|
1496
|
+
{{
|
|
1497
|
+
receipt.extracted.tax === null
|
|
1498
|
+
? '—'
|
|
1499
|
+
: currency.format(receipt.extracted.tax)
|
|
1500
|
+
}}
|
|
1501
|
+
</dd>
|
|
1502
|
+
<dt class="text-gray-500">Date</dt>
|
|
1503
|
+
<dd>
|
|
1504
|
+
{{ receipt.extracted.date ? longDate(receipt.extracted.date) : '—' }}
|
|
1505
|
+
</dd>
|
|
1506
|
+
<dt class="text-gray-500">Paid with</dt>
|
|
1507
|
+
<dd>{{ receipt.extracted.paymentMethod ?? '—' }}</dd>
|
|
1508
|
+
</dl>
|
|
1509
|
+
</section>
|
|
1510
|
+
|
|
1511
|
+
<!-- Properties: the peek is a workbench — the category is the same
|
|
1512
|
+
one-click cell as in the table. -->
|
|
1513
|
+
<section class="flex flex-col gap-1.5">
|
|
1514
|
+
<h3 class="text-xs font-semibold uppercase tracking-wide text-gray-500">
|
|
1515
|
+
Details
|
|
1516
|
+
</h3>
|
|
1517
|
+
<dl class="grid grid-cols-[auto_1fr] items-center gap-x-4 gap-y-1.5">
|
|
1518
|
+
<dt class="text-gray-500">Category</dt>
|
|
1519
|
+
<dd><CategoryCell :receipt="receipt" /></dd>
|
|
1520
|
+
<dt class="text-gray-500">Uploaded</dt>
|
|
1521
|
+
<dd>{{ longDate(receipt.uploadedAt) }}</dd>
|
|
1522
|
+
<dt class="text-gray-500">Report</dt>
|
|
1523
|
+
<dd>{{ receipt.reportName ?? 'Not on a report yet' }}</dd>
|
|
1524
|
+
</dl>
|
|
1525
|
+
</section>
|
|
1526
|
+
|
|
1527
|
+
<!-- Activity feed. -->
|
|
1528
|
+
<section class="flex flex-col gap-1.5">
|
|
1529
|
+
<h3 class="text-xs font-semibold uppercase tracking-wide text-gray-500">
|
|
1530
|
+
Activity
|
|
1531
|
+
</h3>
|
|
1532
|
+
<ul class="flex flex-col gap-2">
|
|
1533
|
+
<li
|
|
1534
|
+
v-for="entry in receipt.activity"
|
|
1535
|
+
:key="entry.id"
|
|
1536
|
+
class="flex gap-2"
|
|
1537
|
+
>
|
|
1538
|
+
<BbIcon
|
|
1539
|
+
class="mt-0.5 shrink-0 text-gray-400"
|
|
1540
|
+
:icon="entry.icon"
|
|
1541
|
+
size="14"
|
|
1542
|
+
/>
|
|
1543
|
+
<div class="flex flex-col">
|
|
1544
|
+
<span>{{ entry.text }}</span>
|
|
1545
|
+
<span class="text-xs text-gray-500"
|
|
1546
|
+
>{{ entry.actor }} · {{ longDate(entry.at) }}</span
|
|
1547
|
+
>
|
|
1548
|
+
</div>
|
|
1549
|
+
</li>
|
|
1550
|
+
</ul>
|
|
1551
|
+
</section>
|
|
1552
|
+
|
|
1553
|
+
<!-- Act here, or leave for the original file. -->
|
|
1554
|
+
<footer class="mt-2 flex items-center gap-2 border-t pt-3">
|
|
1555
|
+
<BbButton
|
|
1556
|
+
v-if="receipt.status === 'draft'"
|
|
1557
|
+
prepend:icon="lucide:send"
|
|
1558
|
+
size="sm"
|
|
1559
|
+
variant="secondary"
|
|
1560
|
+
@click="() => ctx.submitReceipt(receipt!)"
|
|
1561
|
+
>
|
|
1562
|
+
Submit
|
|
1563
|
+
</BbButton>
|
|
1564
|
+
<BbButton
|
|
1565
|
+
append:icon="lucide:arrow-up-right"
|
|
1566
|
+
:href="`/receipts/${receipt.id}/file`"
|
|
1567
|
+
size="sm"
|
|
1568
|
+
variant="ghost"
|
|
1569
|
+
>
|
|
1570
|
+
Download original
|
|
1571
|
+
</BbButton>
|
|
1572
|
+
<BbButton
|
|
1573
|
+
v-if="receipt.status === 'draft'"
|
|
1574
|
+
class="ml-auto"
|
|
1575
|
+
prepend:icon="lucide:trash-2"
|
|
1576
|
+
size="sm"
|
|
1577
|
+
variant="destructive"
|
|
1578
|
+
@click="removeAndClose"
|
|
1579
|
+
>
|
|
1580
|
+
Remove
|
|
1581
|
+
</BbButton>
|
|
1582
|
+
</footer>
|
|
1583
|
+
</div>
|
|
1584
|
+
|
|
1585
|
+
<div v-else class="flex justify-center p-8">
|
|
1586
|
+
<BbSpinner />
|
|
1587
|
+
</div>
|
|
1588
|
+
</template>
|
|
1589
|
+
|
|
1590
|
+
<script setup lang="ts">
|
|
1591
|
+
import { ref, watchEffect } from 'vue';
|
|
1592
|
+
import { BbBadge, BbButton, BbIcon, BbSpinner } from 'bitboss-ui';
|
|
1593
|
+
import CategoryCell from './CategoryCell.vue';
|
|
1594
|
+
import { useUploadCenter } from './context';
|
|
1595
|
+
import { api } from '@/api';
|
|
1596
|
+
import type { ReceiptDetail } from '@/types';
|
|
1597
|
+
|
|
1598
|
+
const props = defineProps<{ receiptId: string }>();
|
|
1599
|
+
|
|
1600
|
+
const ctx = useUploadCenter();
|
|
1601
|
+
|
|
1602
|
+
// Re-fetches whenever the stack shows a different receipt; refreshKey makes
|
|
1603
|
+
// inline edits (category, submit) reflect here immediately.
|
|
1604
|
+
const receipt = ref<ReceiptDetail | null>(null);
|
|
1605
|
+
watchEffect(async () => {
|
|
1606
|
+
void ctx.refreshKey.value;
|
|
1607
|
+
receipt.value = await api.receipts.get(props.receiptId);
|
|
1608
|
+
});
|
|
1609
|
+
|
|
1610
|
+
// deleteReceipt resolves true only when the confirm was accepted — don't
|
|
1611
|
+
// close the peek on a cancelled dialog.
|
|
1612
|
+
const removeAndClose = async () => {
|
|
1613
|
+
if (!receipt.value) return;
|
|
1614
|
+
if (await ctx.deleteReceipt(receipt.value)) ctx.shell.close();
|
|
1615
|
+
};
|
|
1616
|
+
|
|
1617
|
+
const currency = new Intl.NumberFormat('en-US', {
|
|
1618
|
+
style: 'currency',
|
|
1619
|
+
currency: 'USD',
|
|
1620
|
+
});
|
|
1621
|
+
const longDate = (value: string) =>
|
|
1622
|
+
new Date(value).toLocaleDateString('en-US', {
|
|
1623
|
+
month: 'short',
|
|
1624
|
+
day: 'numeric',
|
|
1625
|
+
year: 'numeric',
|
|
1626
|
+
});
|
|
1627
|
+
</script>
|
|
1628
|
+
```
|
|
1629
|
+
|
|
1630
|
+
### MerchantPeek.vue
|
|
1631
|
+
|
|
1632
|
+
```vue
|
|
1633
|
+
<!-- UploadCenter/MerchantPeek.vue -->
|
|
1634
|
+
<template>
|
|
1635
|
+
<div v-if="merchant" class="flex flex-col gap-4 text-sm">
|
|
1636
|
+
<header class="flex items-center gap-3">
|
|
1637
|
+
<div
|
|
1638
|
+
class="grid h-9 w-9 shrink-0 place-items-center rounded-[var(--bb-radius)] border"
|
|
1639
|
+
>
|
|
1640
|
+
<BbIcon class="text-gray-400" icon="lucide:store" size="18" />
|
|
1641
|
+
</div>
|
|
1642
|
+
<div class="flex flex-col">
|
|
1643
|
+
<h2 class="text-base font-semibold">{{ merchant.name }}</h2>
|
|
1644
|
+
<p class="text-xs text-gray-500">
|
|
1645
|
+
{{ merchant.defaultCategoryName }} · this year
|
|
1646
|
+
</p>
|
|
1647
|
+
</div>
|
|
1648
|
+
</header>
|
|
1649
|
+
|
|
1650
|
+
<!-- Stat strip. -->
|
|
1651
|
+
<div class="grid grid-cols-3 gap-2">
|
|
1652
|
+
<div class="rounded-[var(--bb-radius)] border p-2">
|
|
1653
|
+
<p class="text-xs text-gray-500">Total spent</p>
|
|
1654
|
+
<p class="font-semibold">{{ currency.format(merchant.totalSpent) }}</p>
|
|
1655
|
+
</div>
|
|
1656
|
+
<div class="rounded-[var(--bb-radius)] border p-2">
|
|
1657
|
+
<p class="text-xs text-gray-500">Receipts</p>
|
|
1658
|
+
<p class="font-semibold">{{ merchant.receiptCount }}</p>
|
|
1659
|
+
</div>
|
|
1660
|
+
<div class="rounded-[var(--bb-radius)] border p-2">
|
|
1661
|
+
<p class="text-xs text-gray-500">Average</p>
|
|
1662
|
+
<p class="font-semibold">
|
|
1663
|
+
{{ currency.format(merchant.averageAmount) }}
|
|
1664
|
+
</p>
|
|
1665
|
+
</div>
|
|
1666
|
+
</div>
|
|
1667
|
+
|
|
1668
|
+
<!-- Recent receipts: names drill down again — the stack keeps growing. -->
|
|
1669
|
+
<section class="flex flex-col gap-1.5">
|
|
1670
|
+
<h3 class="text-xs font-semibold uppercase tracking-wide text-gray-500">
|
|
1671
|
+
Recent receipts
|
|
1672
|
+
</h3>
|
|
1673
|
+
<ul class="flex flex-col">
|
|
1674
|
+
<li
|
|
1675
|
+
v-for="recent in merchant.recentReceipts"
|
|
1676
|
+
:key="recent.id"
|
|
1677
|
+
class="flex items-center justify-between gap-2 border-b py-1.5 last:border-b-0"
|
|
1678
|
+
>
|
|
1679
|
+
<BbButton
|
|
1680
|
+
size="sm"
|
|
1681
|
+
variant="link"
|
|
1682
|
+
@click="() => ctx.stackReceipt(recent.id)"
|
|
1683
|
+
>
|
|
1684
|
+
{{ recent.filename }}
|
|
1685
|
+
</BbButton>
|
|
1686
|
+
<BbBadge variant="outline">{{ recent.status }}</BbBadge>
|
|
1687
|
+
<span class="text-xs tabular-nums text-gray-500">
|
|
1688
|
+
{{ currency.format(recent.amount) }}
|
|
1689
|
+
</span>
|
|
1690
|
+
</li>
|
|
1691
|
+
</ul>
|
|
1692
|
+
</section>
|
|
1693
|
+
|
|
1694
|
+
<footer class="mt-2 flex items-center gap-2 border-t pt-3">
|
|
1695
|
+
<BbButton
|
|
1696
|
+
append:icon="lucide:arrow-up-right"
|
|
1697
|
+
size="sm"
|
|
1698
|
+
:to="`/expenses/receipts?merchant=${encodeURIComponent(merchant.name)}`"
|
|
1699
|
+
variant="ghost"
|
|
1700
|
+
>
|
|
1701
|
+
All receipts from {{ merchant.name }}
|
|
1702
|
+
</BbButton>
|
|
1703
|
+
</footer>
|
|
1704
|
+
</div>
|
|
1705
|
+
|
|
1706
|
+
<div v-else class="flex justify-center p-8">
|
|
1707
|
+
<BbSpinner />
|
|
1708
|
+
</div>
|
|
1709
|
+
</template>
|
|
1710
|
+
|
|
1711
|
+
<script setup lang="ts">
|
|
1712
|
+
import { ref, watchEffect } from 'vue';
|
|
1713
|
+
import { BbBadge, BbButton, BbIcon, BbSpinner } from 'bitboss-ui';
|
|
1714
|
+
import { useUploadCenter } from './context';
|
|
1715
|
+
import { api } from '@/api';
|
|
1716
|
+
import type { MerchantSummary } from '@/types';
|
|
1717
|
+
|
|
1718
|
+
const props = defineProps<{ merchantName: string }>();
|
|
1719
|
+
|
|
1720
|
+
const ctx = useUploadCenter();
|
|
1721
|
+
|
|
1722
|
+
const merchant = ref<MerchantSummary | null>(null);
|
|
1723
|
+
watchEffect(async () => {
|
|
1724
|
+
merchant.value = await api.merchants.get(props.merchantName);
|
|
1725
|
+
});
|
|
1726
|
+
|
|
1727
|
+
const currency = new Intl.NumberFormat('en-US', {
|
|
1728
|
+
style: 'currency',
|
|
1729
|
+
currency: 'USD',
|
|
1730
|
+
});
|
|
1731
|
+
</script>
|
|
1732
|
+
```
|
|
1733
|
+
|
|
1734
|
+
## Why it is built this way
|
|
1735
|
+
|
|
1736
|
+
- **The context coopts, leaves consume.** `shell`, `table` **and the
|
|
1737
|
+
category catalog** are joined once in `provideUploadCenter()`; thirteen
|
|
1738
|
+
components share the filters, the queue, selection, the peek stack and
|
|
1739
|
+
every action through one `useUploadCenter()` call. No leaf imports
|
|
1740
|
+
`@/api` or a store directly — even the query-aware merchant pill gets
|
|
1741
|
+
its provider (`ctx.searchMerchants`) from the context, so the page has
|
|
1742
|
+
exactly one import surface.
|
|
1743
|
+
- **Capture ≠ transfer.** `BbDropzone`'s documented contract is "these
|
|
1744
|
+
files are valid", nothing more: no request, no progress, no retry. So
|
|
1745
|
+
the dropzone leaf drains its model into `ctx.enqueueFiles()` and resets
|
|
1746
|
+
it, and the context owns the whole transfer lifecycle — one
|
|
1747
|
+
`AbortController` per file in a closure map (not render state), a
|
|
1748
|
+
progress callback writing into the reactive queue row, retry as a plain
|
|
1749
|
+
re-issue of the same upload. Constraint rejects never reach the model;
|
|
1750
|
+
they arrive as typed `@error` events that the leaf maps to sentences and
|
|
1751
|
+
feeds back through `errors` (the display is a round-trip by design).
|
|
1752
|
+
- **Progress ticks don't refetch.** The items function returns the _live_
|
|
1753
|
+
queue row objects ahead of the fetched page, so a progress mutation
|
|
1754
|
+
re-renders exactly the one cell reading it. Membership changes —
|
|
1755
|
+
enqueue, remove, upload completed — are the only events that bump
|
|
1756
|
+
`refreshKey`; a completed upload re-enters as a server draft with the
|
|
1757
|
+
OCR-extracted fields prefilled, which is the row's "done" feedback (no
|
|
1758
|
+
per-file success toast for a 15-file drop).
|
|
1759
|
+
- **Each editable cell type is its own leaf** — `MerchantCell`,
|
|
1760
|
+
`CategoryCell`, `AmountCell`, `DateCell` — never one `EditableCell` with
|
|
1761
|
+
a `type` prop. All four take exactly one data prop (`receipt`) and call
|
|
1762
|
+
one context action; the text/number cells hold a local draft (rung 1)
|
|
1763
|
+
with commit-on-blur/Enter and Esc-revert, the option/date cells commit
|
|
1764
|
+
on pick. Failed saves converge on the same path: `updateReceipt`
|
|
1765
|
+
refetches (reverting the cell to the server value) and toasts
|
|
1766
|
+
`destructive`.
|
|
1767
|
+
- **The table context is the paging and selection spine.**
|
|
1768
|
+
`useBbTableContext('receipts-table', { perPage: 25 })` seeds paging;
|
|
1769
|
+
`fetchRows` writes `table.total`; `BbPagination table-id` joins by id;
|
|
1770
|
+
the bulk bar reads `table.selected` with zero props from the table. The
|
|
1771
|
+
`selectable` predicate limits selection to server-side drafts, so bulk
|
|
1772
|
+
ids are always submittable receipt ids.
|
|
1773
|
+
- **Tabs carry `href` and live counts** (`?tab=drafts`) and the same table
|
|
1774
|
+
is the pane — outside Drafts the cell slots fall through to plain
|
|
1775
|
+
formatted text, so Submitted/Reimbursed are read-only views of the same
|
|
1776
|
+
columns rather than a second table component.
|
|
1777
|
+
- **`peekX` vs `stackX` is the whole stack grammar.** Filename →
|
|
1778
|
+
`peekReceipt` (replaces the stack); merchant name inside the peek →
|
|
1779
|
+
`stackMerchant` (pushes, back arrow appears); a recent receipt inside
|
|
1780
|
+
the merchant peek → `stackReceipt` (deeper still). The grammar lives in
|
|
1781
|
+
the context — a leaf cannot accidentally nuke the stack. The peek is a
|
|
1782
|
+
workbench: one-click category (the same `CategoryCell`), submit,
|
|
1783
|
+
download, confirm-guarded remove.
|
|
1784
|
+
- **Sizing:** topbar follows `md`-first (`compact` search at 28px flush
|
|
1785
|
+
beside the default-size primary); `sm` appears exactly where the
|
|
1786
|
+
doctrine allows — row actions, pills, peek footers, the bulk bar — and
|
|
1787
|
+
the one `xs` (inline Retry) sits inside a table cell, the densest
|
|
1788
|
+
surface on the page. **One `variant="primary"`** (Submit all, with its
|
|
1789
|
+
live count and auto-tracked loading). Destructive actions — single,
|
|
1790
|
+
bulk, in-peek — all ride `useConfirm` with the filename or count in the
|
|
1791
|
+
message.
|
|
1792
|
+
|
|
1793
|
+
## Variations
|
|
1794
|
+
|
|
1795
|
+
- **Expense reports**: group receipts into named reports (a `reportId`
|
|
1796
|
+
column + a "Add to report" bulk action feeding a `BbSelectPopover` of
|
|
1797
|
+
open reports); the Submit-all primary becomes "Submit report".
|
|
1798
|
+
- **Policy checks**: server flags out-of-policy drafts
|
|
1799
|
+
(`receipt.policyWarning`); tint the row, badge the amount cell, and
|
|
1800
|
+
render a `BbAlert` above the table summarizing the count.
|
|
1801
|
+
- **Camera-first mobile**: keep `accept` image-heavy and rely on the
|
|
1802
|
+
native picker offering the camera; the dropzone slot collapses to a
|
|
1803
|
+
single "Snap a receipt" affordance under the mobile breakpoint.
|
|
1804
|
+
- **Auto-submit on high confidence**: when `extracted.confidence` ≥ 95 and
|
|
1805
|
+
all four fields resolved, submit server-side on upload and toast an
|
|
1806
|
+
undo — the Drafts tab then only holds receipts needing review.
|