bitboss-ui 2.1.113 → 2.1.115
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/dist/ai/BaseButton.md +448 -0
- package/dist/ai/BaseCheckbox.md +494 -0
- package/dist/ai/BaseCheckboxGroup.md +597 -0
- package/dist/ai/BaseColorInput.md +461 -0
- package/dist/ai/BaseDatePicker.md +739 -0
- package/dist/ai/BaseDatePickerInput.md +1517 -0
- package/dist/ai/BaseDialog.md +610 -0
- package/dist/ai/BaseInputContainer.md +570 -0
- package/dist/ai/BaseNumberInput.md +509 -0
- package/dist/ai/BaseRadio.md +405 -0
- package/dist/ai/BaseRadioGroup.md +535 -0
- package/dist/ai/BaseRating.md +489 -0
- package/dist/ai/BaseSelect.md +1720 -0
- package/dist/ai/BaseSlider.md +871 -0
- package/dist/ai/BaseSwitch.md +322 -0
- package/dist/ai/BaseSwitchGroup.md +298 -0
- package/dist/ai/BaseTag.md +624 -0
- package/dist/ai/BaseTextInput.md +392 -0
- package/dist/ai/BaseTextarea.md +398 -0
- package/dist/ai/BbAccordion.md +135 -0
- package/dist/ai/BbAlert.md +226 -0
- package/dist/ai/BbAvatar.md +200 -0
- package/dist/ai/BbBadge.md +185 -0
- package/dist/ai/BbBreadcrumbs.md +536 -0
- package/dist/ai/BbButton.md +687 -0
- package/dist/ai/BbCheckbox.md +280 -0
- package/dist/ai/BbCheckboxGroup.md +387 -0
- package/dist/ai/BbChip.md +148 -0
- package/dist/ai/BbCollapsible.md +119 -0
- package/dist/ai/BbColorInput.md +345 -0
- package/dist/ai/BbColorPalette.md +360 -0
- package/dist/ai/BbConfirm.md +160 -0
- package/dist/ai/BbDatePickerInput.md +414 -0
- package/dist/ai/BbDialog.md +135 -0
- package/dist/ai/BbDropdown.md +765 -0
- package/dist/ai/BbDropdownButton.md +629 -0
- package/dist/ai/BbDropzone.md +504 -0
- package/dist/ai/BbIcon.md +238 -0
- package/dist/ai/BbIntersection.md +121 -0
- package/dist/ai/BbNumberInput.md +372 -0
- package/dist/ai/BbOffCanvas.md +549 -0
- package/dist/ai/BbPagination.md +562 -0
- package/dist/ai/BbPopover.md +580 -0
- package/dist/ai/BbProgress.md +97 -0
- package/dist/ai/BbRadio.md +256 -0
- package/dist/ai/BbRadioGroup.md +373 -0
- package/dist/ai/BbRating.md +245 -0
- package/dist/ai/BbRatio.md +62 -0
- package/dist/ai/BbRows.md +307 -0
- package/dist/ai/BbSelect.md +562 -0
- package/dist/ai/BbSelectPopover.md +2010 -0
- package/dist/ai/BbSlider.md +274 -0
- package/dist/ai/BbSmoothHeight.md +167 -0
- package/dist/ai/BbSpinner.md +154 -0
- package/dist/ai/BbSwitch.md +151 -0
- package/dist/ai/BbSwitchGroup.md +237 -0
- package/dist/ai/BbTab.md +954 -0
- package/dist/ai/BbTable.md +1624 -0
- package/dist/ai/BbTag.md +315 -0
- package/dist/ai/BbTextInput.md +357 -0
- package/dist/ai/BbTextarea.md +277 -0
- package/dist/ai/BbToast.md +219 -0
- package/dist/ai/BbTooltip.md +353 -0
- package/dist/ai/BbTree.md +271 -0
- package/dist/ai/ChipsBox.md +211 -0
- package/dist/ai/ClearableButton.md +67 -0
- package/dist/ai/CommaBox.md +212 -0
- package/dist/ai/CommonInputInnerContainer.md +419 -0
- package/dist/ai/CommonInputOuterContainer.md +56 -0
- package/dist/ai/CommonPopover.md +446 -0
- package/dist/ai/ErrorIcon.md +61 -0
- package/dist/ai/FlatListBox.md +382 -0
- package/dist/ai/GroupedListBox.md +538 -0
- package/dist/ai/ListBox.md +234 -0
- package/dist/ai/OptionsContainer.md +257 -0
- package/dist/ai/index.md +124 -0
- package/dist/components/BaseButton/BaseButton.vue.d.ts +2 -163
- package/dist/components/BaseButton/types.d.ts +158 -0
- package/dist/components/BaseCheckbox/BaseCheckbox.vue.d.ts +4 -4
- package/dist/components/BaseCheckboxGroup/BaseCheckboxGroup.vue.d.ts +2 -2
- package/dist/components/BaseCheckboxGroup/types.d.ts +16 -9
- package/dist/components/BaseColorInput/BaseColorInput.vue.d.ts +12 -52
- package/dist/components/BaseDatePicker/BaseDatePicker.vue.d.ts +4 -76
- package/dist/components/BaseDatePicker/types.d.ts +100 -0
- package/dist/components/BaseDatePickerInput/BaseDatePickerInput.vue.d.ts +18 -315
- package/dist/components/BaseDatePickerInput/types.d.ts +206 -0
- package/dist/components/BaseDialog/BaseDialog.vue.d.ts +6 -156
- package/dist/components/BaseDialog/types.d.ts +180 -0
- package/dist/components/BaseInputContainer/BaseInputContainer.vue.d.ts +1 -107
- package/dist/components/BaseInputContainer/types.d.ts +126 -0
- package/dist/components/BaseNumberInput/BaseNumberInput.vue.d.ts +7 -170
- package/dist/components/BaseNumberInput/types.d.ts +191 -0
- package/dist/components/BaseRadio/BaseRadio.vue.d.ts +6 -119
- package/dist/components/BaseRadio/types.d.ts +173 -0
- package/dist/components/BaseRadioGroup/BaseRadioGroup.vue.d.ts +4 -274
- package/dist/components/BaseRadioGroup/types.d.ts +240 -0
- package/dist/components/BaseRating/BaseRating.vue.d.ts +5 -106
- package/dist/components/BaseRating/types.d.ts +144 -0
- package/dist/components/BaseSelect/BaseSelect.vue.d.ts +2 -363
- package/dist/components/BaseSelect/types.d.ts +457 -0
- package/dist/components/BaseSlider/BaseSlider.vue.d.ts +6 -178
- package/dist/components/BaseSlider/types.d.ts +201 -0
- package/dist/components/BaseSwitch/BaseSwitch.vue.d.ts +7 -35
- package/dist/components/BaseSwitch/types.d.ts +25 -0
- package/dist/components/BaseSwitchGroup/BaseSwitchGroup.vue.d.ts +5 -11
- package/dist/components/BaseSwitchGroup/types.d.ts +8 -0
- package/dist/components/BaseTag/BaseTag.vue.d.ts +27 -222
- package/dist/components/BaseTag/types.d.ts +136 -0
- package/dist/components/BaseTextInput/BaseTextInput.vue.d.ts +5 -141
- package/dist/components/BaseTextInput/types.d.ts +166 -0
- package/dist/components/BaseTextarea/BaseTextarea.vue.d.ts +5 -131
- package/dist/components/BaseTextarea/types.d.ts +151 -0
- package/dist/components/BbAccordion/BbAccordion.vue.d.ts +3 -51
- package/dist/components/BbAccordion/types.d.ts +32 -0
- package/dist/components/BbAlert/BbAlert.vue.d.ts +3 -50
- package/dist/components/BbAlert/types.d.ts +42 -0
- package/dist/components/BbAvatar/BbAvatar.vue.d.ts +3 -23
- package/dist/components/BbAvatar/types.d.ts +34 -0
- package/dist/components/BbBadge/BbBadge.vue.d.ts +3 -40
- package/dist/components/BbBadge/types.d.ts +30 -0
- package/dist/components/BbBreadcrumbs/BbBreadcrumbs.vue.d.ts +14 -178
- package/dist/components/BbBreadcrumbs/types.d.ts +109 -0
- package/dist/components/BbButton/BbButton.vue.d.ts +4 -163
- package/dist/components/BbButton/types.d.ts +159 -0
- package/dist/components/BbCheckbox/BbCheckbox.vue.d.ts +7 -165
- package/dist/components/BbCheckbox/types.d.ts +130 -0
- package/dist/components/BbCheckboxGroup/BbCheckboxGroup.vue.d.ts +7 -324
- package/dist/components/BbCheckboxGroup/types.d.ts +189 -0
- package/dist/components/BbChip/BbChip.vue.d.ts +6 -28
- package/dist/components/BbChip/types.d.ts +23 -0
- package/dist/components/BbCollapsible/BbCollapsible.vue.d.ts +3 -24
- package/dist/components/BbCollapsible/types.d.ts +20 -0
- package/dist/components/BbColorInput/BbColorInput.vue.d.ts +10 -151
- package/dist/components/BbColorInput/types.d.ts +131 -0
- package/dist/components/BbColorPalette/BbColorPalette.vue.d.ts +2 -112
- package/dist/components/BbColorPalette/types.d.ts +127 -0
- package/dist/components/BbDatePickerInput/BbDatePickerInput.vue.d.ts +6 -212
- package/dist/components/BbDatePickerInput/types.d.ts +180 -0
- package/dist/components/BbDialog/BbDialog.vue.d.ts +2 -2
- package/dist/components/BbDialog/types.d.ts +1 -0
- package/dist/components/BbDropdown/BbDropdown.vue.d.ts +21 -247
- package/dist/components/BbDropdown/types.d.ts +147 -0
- package/dist/components/BbDropdownButton/BbDropdownButton.vue.d.ts +16 -209
- package/dist/components/BbDropdownButton/types.d.ts +114 -0
- package/dist/components/BbDropzone/BbDropzone.vue.d.ts +7 -86
- package/dist/components/BbDropzone/types.d.ts +67 -0
- package/dist/components/BbIcon/BbIcon.vue.d.ts +2 -26
- package/dist/components/BbIcon/types.d.ts +28 -0
- package/dist/components/BbIntersection/BbIntersection.vue.d.ts +3 -41
- package/dist/components/BbIntersection/types.d.ts +36 -0
- package/dist/components/BbNumberInput/BbNumberInput.vue.d.ts +44 -175
- package/dist/components/BbNumberInput/types.d.ts +130 -0
- package/dist/components/BbOffCanvas/BbOffCanvas.vue.d.ts +5 -93
- package/dist/components/BbOffCanvas/types.d.ts +97 -0
- package/dist/components/BbPagination/BbPagination.vue.d.ts +4 -87
- package/dist/components/BbPagination/types.d.ts +80 -0
- package/dist/components/BbPopover/BbPopover.vue.d.ts +9 -135
- package/dist/components/BbPopover/types.d.ts +99 -0
- package/dist/components/BbProgress/BbProgress.vue.d.ts +2 -14
- package/dist/components/BbProgress/types.d.ts +20 -0
- package/dist/components/BbRadio/BbRadio.vue.d.ts +7 -150
- package/dist/components/BbRadio/types.d.ts +117 -0
- package/dist/components/BbRadioGroup/BbRadioGroup.vue.d.ts +7 -322
- package/dist/components/BbRadioGroup/types.d.ts +182 -0
- package/dist/components/BbRating/BbRating.vue.d.ts +10 -113
- package/dist/components/BbRating/types.d.ts +105 -0
- package/dist/components/BbRatio/BbRatio.vue.d.ts +3 -18
- package/dist/components/BbRatio/types.d.ts +15 -0
- package/dist/components/BbSelect/BbSelect.vue.d.ts +7 -375
- package/dist/components/BbSelect/types.d.ts +351 -0
- package/dist/components/BbSelectPopover/BbSelectPopover.vue.d.ts +1 -1
- package/dist/components/BbSelectPopover/types.d.ts +351 -0
- package/dist/components/BbSlider/BbSlider.vue.d.ts +10 -129
- package/dist/components/BbSlider/types.d.ts +123 -0
- package/dist/components/BbSmoothHeight/BbSmoothHeight.vue.d.ts +2 -23
- package/dist/components/BbSmoothHeight/types.d.ts +24 -0
- package/dist/components/BbSpinner/BbSpinner.vue.d.ts +3 -5
- package/dist/components/BbSpinner/types.d.ts +8 -0
- package/dist/components/BbSwitch/BbSwitch.vue.d.ts +9 -65
- package/dist/components/BbSwitch/types.d.ts +29 -0
- package/dist/components/BbSwitchGroup/BbSwitchGroup.vue.d.ts +7 -190
- package/dist/components/BbSwitchGroup/types.d.ts +81 -0
- package/dist/components/BbTab/BbTab.vue.d.ts +9 -247
- package/dist/components/BbTab/types.d.ts +186 -0
- package/dist/components/BbTag/BbTag.vue.d.ts +6 -156
- package/dist/components/BbTag/types.d.ts +158 -0
- package/dist/components/BbTextInput/BbTextInput.vue.d.ts +10 -152
- package/dist/components/BbTextInput/types.d.ts +137 -0
- package/dist/components/BbTextarea/BbTextarea.vue.d.ts +10 -142
- package/dist/components/BbTextarea/types.d.ts +123 -0
- package/dist/components/BbToast/BbToast.vue.d.ts +2 -6
- package/dist/components/BbToast/types.d.ts +8 -0
- package/dist/components/BbTooltip/BbTooltip.vue.d.ts +8 -65
- package/dist/components/BbTooltip/types.d.ts +55 -0
- package/dist/components/BbTree/BbTree.vue.d.ts +2 -65
- package/dist/components/BbTree/types.d.ts +69 -0
- package/dist/components/{ChipsBox.vue.d.ts → ChipsBox/ChipsBox.vue.d.ts} +5 -6
- package/dist/components/ChipsBox/types.d.ts +14 -0
- package/dist/components/{ClearableButton.vue.d.ts → ClearableButton/ClearableButton.vue.d.ts} +2 -0
- package/dist/components/ClearableButton/types.d.ts +3 -0
- package/dist/components/{CommaBox.vue.d.ts → CommaBox/CommaBox.vue.d.ts} +5 -6
- package/dist/components/CommaBox/types.d.ts +14 -0
- package/dist/components/CommonInputInnerContainer/CommonInputInnerContainer.vue.d.ts +25 -0
- package/dist/components/CommonInputInnerContainer/types.d.ts +47 -0
- package/dist/components/CommonInputOuterContainer/CommonInputOuterContainer.vue.d.ts +17 -0
- package/dist/components/CommonInputOuterContainer/types.d.ts +16 -0
- package/dist/components/{CommonPopover.vue.d.ts → CommonPopover/CommonPopover.vue.d.ts} +5 -30
- package/dist/components/CommonPopover/types.d.ts +43 -0
- package/dist/components/{ErrorIcon.vue.d.ts → ErrorIcon/ErrorIcon.vue.d.ts} +2 -0
- package/dist/components/ErrorIcon/types.d.ts +3 -0
- package/dist/components/FlatListBox/types.d.ts +97 -0
- package/dist/components/GroupedListBox/types.d.ts +118 -0
- package/dist/components/ListBox/ListBox.vue.d.ts +30 -0
- package/dist/components/ListBox/types.d.ts +133 -0
- package/dist/components/OptionsContainer/OptionsContainer.vue.d.ts +13 -0
- package/dist/components/OptionsContainer/types.d.ts +96 -0
- package/dist/composables/useBbConfig.d.ts +1 -1
- package/dist/composables/useConfirm.d.ts +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.ts +18 -18
- package/dist/index109.js +9 -9
- package/dist/index110.js +50 -49
- package/dist/index114.js +1 -1
- package/dist/index118.js +1 -1
- package/dist/index122.js +1 -0
- package/dist/index124.js +4 -4
- package/dist/index126.js +13 -13
- package/dist/index132.js +22 -19
- package/dist/index134.js +1 -1
- package/dist/index136.js +5 -5
- package/dist/index138.js +1 -1
- package/dist/index14.js +1 -1
- package/dist/index140.js +18 -17
- package/dist/index144.js +1 -1
- package/dist/index146.js +2 -2
- package/dist/index149.js +2 -2
- package/dist/index16.js +3 -3
- package/dist/index18.js +3 -3
- package/dist/index20.js +70 -59
- package/dist/index22.js +14 -14
- package/dist/index221.js +138 -2
- package/dist/index222.js +2 -138
- package/dist/index224.js +5 -34
- package/dist/index225.js +7 -32
- package/dist/index226.js +32 -26
- package/dist/index227.js +7 -0
- package/dist/index228.js +5 -5
- package/dist/index229.js +5 -8
- package/dist/index230.js +5 -7
- package/dist/index231.js +3 -2
- package/dist/index232.js +2 -9
- package/dist/index233.js +6 -13
- package/dist/index234.js +8 -3
- package/dist/index235.js +268 -2
- package/dist/index236.js +52 -11
- package/dist/index237.js +50 -6
- package/dist/index238.js +32 -3
- package/dist/index239.js +60 -3
- package/dist/index24.js +10 -10
- package/dist/index240.js +13 -2
- package/dist/index241.js +187 -17
- package/dist/index242.js +3 -12
- package/dist/index243.js +2 -51
- package/dist/index244.js +2 -18
- package/dist/index245.js +2 -12
- package/dist/index246.js +12 -16
- package/dist/index247.js +11 -28
- package/dist/index248.js +48 -15
- package/dist/index249.js +17 -4
- package/dist/index250.js +2 -2
- package/dist/index252.js +2 -2
- package/dist/index254.js +3 -135
- package/dist/index255.js +4 -0
- package/dist/index256.js +4 -107
- package/dist/index257.js +19 -12
- package/dist/index258.js +6 -2
- package/dist/index259.js +16 -7
- package/dist/index26.js +3 -3
- package/dist/index260.js +86 -7
- package/dist/index262.js +32 -0
- package/dist/index263.js +18 -5
- package/dist/index264.js +12 -5
- package/dist/index265.js +18 -5
- package/dist/index266.js +2 -5
- package/dist/index267.js +7 -5
- package/dist/index268.js +7 -5
- package/dist/index269.js +3 -67
- package/dist/index270.js +4 -33
- package/dist/index271.js +5 -2
- package/dist/index272.js +5 -2
- package/dist/index273.js +5 -3
- package/dist/index274.js +135 -4
- package/dist/index276.js +9 -6
- package/dist/index277.js +7 -11
- package/dist/index278.js +23 -5
- package/dist/index279.js +3 -5
- package/dist/index28.js +57 -55
- package/dist/index280.js +21 -266
- package/dist/index281.js +364 -43
- package/dist/index283.js +32 -31
- package/dist/index284.js +3 -60
- package/dist/index285.js +25 -4
- package/dist/index286.js +3 -20
- package/dist/index287.js +18 -5
- package/dist/index288.js +12 -373
- package/dist/index289.js +109 -0
- package/dist/index290.js +11 -6
- package/dist/index291.js +66 -15
- package/dist/index292.js +32 -10
- package/dist/index294.js +5 -8
- package/dist/index295.js +9 -20
- package/dist/index296.js +2 -8
- package/dist/index297.js +9 -23
- package/dist/index298.js +52 -24
- package/dist/index299.js +5 -188
- package/dist/index30.js +3 -3
- package/dist/index300.js +21 -3
- package/dist/index301.js +28 -3
- package/dist/index303.js +9 -0
- package/dist/index304.js +2 -7
- package/dist/index305.js +280 -3
- package/dist/index306.js +2 -2
- package/dist/index307.js +16 -5
- package/dist/index308.js +2 -7
- package/dist/index309.js +16 -3
- package/dist/index310.js +2 -3
- package/dist/index311.js +27 -3
- package/dist/index312.js +2 -2
- package/dist/index313.js +2 -28
- package/dist/index314.js +2 -17
- package/dist/index315.js +2 -4
- package/dist/index316.js +1 -1
- package/dist/index317.js +28 -3
- package/dist/index318.js +2 -280
- package/dist/index319.js +7 -2
- package/dist/index32.js +2 -2
- package/dist/index320.js +719 -125
- package/dist/index321.js +366 -2
- package/dist/index322.js +56 -14
- package/dist/index323.js +4 -2
- package/dist/index324.js +3 -16
- package/dist/index325.js +17 -2
- package/dist/index326.js +3 -16
- package/dist/index327.js +3 -2
- package/dist/index328.js +3 -19
- package/dist/index329.js +3 -2
- package/dist/index330.js +120 -22
- package/dist/index331.js +2 -2
- package/dist/index332.js +15 -2
- package/dist/index333.js +2 -2
- package/dist/index334.js +19 -2
- package/dist/index335.js +2 -2
- package/dist/index336.js +5 -2
- package/dist/index337.js +5 -3
- package/dist/index338.js +2 -4
- package/dist/index339.js +4 -719
- package/dist/index34.js +8 -8
- package/dist/index340.js +2 -366
- package/dist/index341.js +3 -57
- package/dist/index342.js +3 -6
- package/dist/index343.js +6 -5
- package/dist/index344.js +6 -34
- package/dist/index345.js +17 -127
- package/dist/index346.js +7 -396
- package/dist/index347.js +14 -199
- package/dist/index348.js +5 -259
- package/dist/index349.js +6 -227
- package/dist/index352.js +35 -2
- package/dist/index353.js +129 -2
- package/dist/index354.js +378 -114
- package/dist/index355.js +92 -6
- package/dist/index356.js +226 -17
- package/dist/index357.js +22 -9
- package/dist/index359.js +7 -5
- package/dist/index36.js +4 -4
- package/dist/index360.js +200 -7
- package/dist/index361.js +255 -18
- package/dist/index362.js +136 -0
- package/dist/index363.js +2 -93
- package/dist/index364.js +2 -441
- package/dist/index365.js +427 -114
- package/dist/index366.js +127 -46
- package/dist/index367.js +44 -67
- package/dist/index368.js +66 -516
- package/dist/index369.js +515 -45
- package/dist/index370.js +52 -0
- package/dist/index38.js +133 -131
- package/dist/index40.js +8 -8
- package/dist/index42.js +2 -2
- package/dist/index44.js +16 -15
- package/dist/index46.js +4 -4
- package/dist/index50.js +28 -25
- package/dist/index54.js +1 -1
- package/dist/index56.js +1 -1
- package/dist/index58.js +2 -2
- package/dist/index60.js +2 -2
- package/dist/index62.js +5 -5
- package/dist/index66.js +3 -1
- package/dist/index68.js +1 -1
- package/dist/index74.js +4 -4
- package/dist/index82.js +6 -6
- package/dist/index84.js +1 -1
- package/dist/index86.js +2 -2
- package/dist/index88.js +3 -3
- package/dist/index90.js +1 -1
- package/dist/index93.js +3 -3
- package/dist/index95.js +2 -2
- package/dist/index97.js +5 -5
- package/dist/index99.js +1 -1
- package/dist/utilities/functions/parseSize.d.ts +1 -1
- package/package.json +5 -3
- package/dist/components/CommonInputInnerContainer.vue.d.ts +0 -81
- package/dist/components/CommonInputOuterContainer.vue.d.ts +0 -41
- package/dist/components/FlatListBox.vue.d.ts +0 -119
- package/dist/components/GroupedListBox.vue.d.ts +0 -153
- package/dist/components/ListBox.vue.d.ts +0 -170
- package/dist/components/OptionsContainer.vue.d.ts +0 -172
- package/dist/index261.js +0 -88
- package/dist/index275.js +0 -25
- package/dist/index282.js +0 -54
- package/dist/index293.js +0 -5
- package/dist/index302.js +0 -55
- package/dist/index358.js +0 -17
package/dist/ai/BbTab.md
ADDED
|
@@ -0,0 +1,954 @@
|
|
|
1
|
+
# BbTab
|
|
2
|
+
|
|
3
|
+
## Template & Script
|
|
4
|
+
|
|
5
|
+
```vue
|
|
6
|
+
<template>
|
|
7
|
+
<div
|
|
8
|
+
:id="id"
|
|
9
|
+
ref="tabs"
|
|
10
|
+
class="bb-tab"
|
|
11
|
+
:class="{
|
|
12
|
+
[`bb-tab--${direction}`]: true,
|
|
13
|
+
'bb-tab--disabled': disabled,
|
|
14
|
+
}"
|
|
15
|
+
>
|
|
16
|
+
<div class="bb-tab__label-boundary">
|
|
17
|
+
<span><slot name="header:prepend" v-bind="scrollStatus"></slot></span>
|
|
18
|
+
<ul
|
|
19
|
+
ref="tablist"
|
|
20
|
+
class="bb-tab__label-container"
|
|
21
|
+
:class="{ 'bb-tab__label-container--no-transition': !highlightReady }"
|
|
22
|
+
role="tablist"
|
|
23
|
+
:style="{
|
|
24
|
+
'--inner-width': `${containerWidth}px`,
|
|
25
|
+
'--width': `${highlightWidth}px`,
|
|
26
|
+
'--height': `${highlightHeight}px`,
|
|
27
|
+
'--left': `${highlightLeft}px`,
|
|
28
|
+
'--top': `${highlightTop}px`,
|
|
29
|
+
}"
|
|
30
|
+
>
|
|
31
|
+
<li v-for="tab in mappedItems" :key="tab.tabId" role="presentation">
|
|
32
|
+
<BaseButton
|
|
33
|
+
:id="tab.tabId"
|
|
34
|
+
:aria-selected="tab.ariaSelected"
|
|
35
|
+
:class="tab.tabClasses"
|
|
36
|
+
:disabled="tab.disabled"
|
|
37
|
+
:role="'tab'"
|
|
38
|
+
:tabindex="tab.tabindex"
|
|
39
|
+
@click="tab.onClick"
|
|
40
|
+
@keydown="tab.onKeydown"
|
|
41
|
+
>
|
|
42
|
+
<slot
|
|
43
|
+
:current="selectedTab"
|
|
44
|
+
:current-index="currentTabIndex"
|
|
45
|
+
:disabled="tab.disabled"
|
|
46
|
+
:go-to="goTo"
|
|
47
|
+
:is-first="isFirst"
|
|
48
|
+
:is-last="isLast"
|
|
49
|
+
:name="tab.labelSlotName"
|
|
50
|
+
:selected="tab.active"
|
|
51
|
+
:text="tab.label"
|
|
52
|
+
:value="tab.key"
|
|
53
|
+
>
|
|
54
|
+
<slot
|
|
55
|
+
:current="selectedTab"
|
|
56
|
+
:current-index="currentTabIndex"
|
|
57
|
+
:disabled="tab.disabled"
|
|
58
|
+
:go-to="goTo"
|
|
59
|
+
:is-first="isFirst"
|
|
60
|
+
:is-last="isLast"
|
|
61
|
+
name="label"
|
|
62
|
+
:selected="tab.active"
|
|
63
|
+
:text="tab.label"
|
|
64
|
+
:value="tab.key"
|
|
65
|
+
>
|
|
66
|
+
<span class="bb-tab__label">{{ tab.label }}</span>
|
|
67
|
+
</slot>
|
|
68
|
+
</slot>
|
|
69
|
+
</BaseButton>
|
|
70
|
+
</li>
|
|
71
|
+
</ul>
|
|
72
|
+
<span><slot name="header:append" v-bind="scrollStatus"></slot></span>
|
|
73
|
+
</div>
|
|
74
|
+
<div class="bb-tab__panes-container">
|
|
75
|
+
<component :is="props.animateY ? BbSmoothHeight : 'div'">
|
|
76
|
+
<template v-for="tab in mappedItems" :key="tab.tabId">
|
|
77
|
+
<Transition
|
|
78
|
+
:css="props.animateX"
|
|
79
|
+
:name="`tab-slide-${animationDirection}`"
|
|
80
|
+
>
|
|
81
|
+
<section
|
|
82
|
+
v-if="tab.active || props.eager || tab.eager"
|
|
83
|
+
:id="tab.panelId"
|
|
84
|
+
:aria-labelledby="tab.tabId"
|
|
85
|
+
:class="tab.paneClasses"
|
|
86
|
+
role="tabpanel"
|
|
87
|
+
:tabindex="tab.tabindex"
|
|
88
|
+
>
|
|
89
|
+
<slot
|
|
90
|
+
:current="selectedTab"
|
|
91
|
+
:current-index="currentTabIndex"
|
|
92
|
+
:disabled="tab.disabled"
|
|
93
|
+
:go-to="goTo"
|
|
94
|
+
:is-first="isFirst"
|
|
95
|
+
:is-last="isLast"
|
|
96
|
+
:name="tab.key"
|
|
97
|
+
:selected="tab.active"
|
|
98
|
+
:text="tab.label"
|
|
99
|
+
:value="tab.key"
|
|
100
|
+
></slot>
|
|
101
|
+
</section>
|
|
102
|
+
</Transition>
|
|
103
|
+
</template>
|
|
104
|
+
</component>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
</template>
|
|
108
|
+
|
|
109
|
+
<script setup lang="ts" generic="K extends string = string">
|
|
110
|
+
import { ref, watch, getCurrentInstance, computed, nextTick } from 'vue';
|
|
111
|
+
import { useId } from '@/composables/useId';
|
|
112
|
+
import { useRoute } from '@/composables/useRoute';
|
|
113
|
+
import { useRouter } from '@/composables/useRouter';
|
|
114
|
+
import { useScroll, useResizeObserver } from '@vueuse/core';
|
|
115
|
+
import BaseButton from '../BaseButton/BaseButton.vue';
|
|
116
|
+
import BbSmoothHeight from '../BbSmoothHeight/BbSmoothHeight.vue';
|
|
117
|
+
import { onMounted } from 'vue';
|
|
118
|
+
import { useFrameworkDetection } from '@/composables/useFrameworkDetection';
|
|
119
|
+
import { merge } from '@/utilities/functions/merge';
|
|
120
|
+
import { isNil } from '@/utilities/functions/isNil';
|
|
121
|
+
import { useQuery } from '@/composables/useQuery';
|
|
122
|
+
import { hash } from '@/utilities/functions/hash';
|
|
123
|
+
import { isNotNil } from '@/utilities/functions/isNotNil';
|
|
124
|
+
import { useLogger } from '@/composables/useLogger';
|
|
125
|
+
import type {
|
|
126
|
+
BbTabProps,
|
|
127
|
+
BbTabEvents,
|
|
128
|
+
BbTabItem,
|
|
129
|
+
BbTabInertiaOptions,
|
|
130
|
+
BbTabSlots,
|
|
131
|
+
} from './types';
|
|
132
|
+
export type {
|
|
133
|
+
BbTabProps,
|
|
134
|
+
BbTabEvents,
|
|
135
|
+
BbTabItem,
|
|
136
|
+
BbTabInertiaOptions,
|
|
137
|
+
BbTabSlots,
|
|
138
|
+
} from './types';
|
|
139
|
+
|
|
140
|
+
const props = withDefaults(defineProps<BbTabProps<K>>(), {
|
|
141
|
+
animateX: true,
|
|
142
|
+
animateY: true,
|
|
143
|
+
direction: 'horizontal',
|
|
144
|
+
querykey: 'tab',
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const emit = defineEmits<BbTabEvents>();
|
|
148
|
+
|
|
149
|
+
defineSlots<BbTabSlots<K>>();
|
|
150
|
+
|
|
151
|
+
const route = useRoute();
|
|
152
|
+
const router = useRouter();
|
|
153
|
+
const { error } = useLogger();
|
|
154
|
+
|
|
155
|
+
const tabs = ref();
|
|
156
|
+
|
|
157
|
+
const id = props.id ?? useId().id.value;
|
|
158
|
+
const instanceid = `bb-tab_${id}`;
|
|
159
|
+
|
|
160
|
+
const tablist = ref<HTMLElement>();
|
|
161
|
+
const { isScrolling, arrivedState } = useScroll(tablist);
|
|
162
|
+
const scrollStatus = computed(() => ({
|
|
163
|
+
isScrolling: isScrolling.value,
|
|
164
|
+
left: arrivedState.left,
|
|
165
|
+
right: arrivedState.right,
|
|
166
|
+
}));
|
|
167
|
+
|
|
168
|
+
const { isInertia } = useFrameworkDetection();
|
|
169
|
+
const instance = getCurrentInstance();
|
|
170
|
+
|
|
171
|
+
const selectedTab = ref<BbTabItem<K>['key'] | null>(null);
|
|
172
|
+
|
|
173
|
+
const currentTabIndex = computed(() =>
|
|
174
|
+
props.items.findIndex((el) => el.key === selectedTab.value)
|
|
175
|
+
);
|
|
176
|
+
const isFirst = computed(() => currentTabIndex.value === 0);
|
|
177
|
+
const isLast = computed(() => currentTabIndex.value === props.items.length - 1);
|
|
178
|
+
|
|
179
|
+
props.items.forEach((item) => {
|
|
180
|
+
if (!item.key) {
|
|
181
|
+
error(`A tab item has no key: ${JSON.stringify(item, null, 2)}`);
|
|
182
|
+
} else if ((props.server || item.server) && !props.navigation) {
|
|
183
|
+
error(
|
|
184
|
+
`A tab item has a server property but the navigation prop is false: ${JSON.stringify(item, null, 2)}`
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
const queryTabParam = computed(() => {
|
|
190
|
+
const query = useQuery();
|
|
191
|
+
return [query[props.querykey]].flat()[0] as K | null;
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
/*
|
|
195
|
+
This section retrieves the current tab that should be selected
|
|
196
|
+
*/
|
|
197
|
+
// Retrieve tab from url on load for vue apps
|
|
198
|
+
const getInitialPosition = () => {
|
|
199
|
+
const queryTab = queryTabParam.value as K | null;
|
|
200
|
+
|
|
201
|
+
if (props.navigation && queryTab) {
|
|
202
|
+
selectedTab.value = queryTab;
|
|
203
|
+
} else if (props.modelValue) {
|
|
204
|
+
selectedTab.value = props.modelValue;
|
|
205
|
+
|
|
206
|
+
// Else whatever tab is passed first
|
|
207
|
+
} else if (props.items[0]) {
|
|
208
|
+
const firstSelectableTab = props.items.find((item) => !item.disabled);
|
|
209
|
+
if (!firstSelectableTab) {
|
|
210
|
+
throw new Error(
|
|
211
|
+
'No tab has been selected so it defaulted to the first available. Still, all the tabs are disabled so no tab can be selected.'
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
const key = firstSelectableTab.key;
|
|
215
|
+
selectedTab.value = key;
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
getInitialPosition();
|
|
219
|
+
/*
|
|
220
|
+
Once the current tab is found update external variables
|
|
221
|
+
*/
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Merges component-level Inertia defaults with per-item overrides.
|
|
225
|
+
* Item values always take precedence.
|
|
226
|
+
*/
|
|
227
|
+
const mergeInertiaOptions = (tab: BbTabItem<K>): BbTabInertiaOptions => ({
|
|
228
|
+
headers: tab.headers ?? props.headers,
|
|
229
|
+
only: tab.only ?? props.only,
|
|
230
|
+
preserveScroll: tab.preserveScroll ?? props.preserveScroll,
|
|
231
|
+
preserveState: tab.preserveState ?? props.preserveState,
|
|
232
|
+
replace: props.replace,
|
|
233
|
+
onBefore: tab.onBefore ?? props.onBefore,
|
|
234
|
+
onStart: tab.onStart ?? props.onStart,
|
|
235
|
+
onProgress: tab.onProgress ?? props.onProgress,
|
|
236
|
+
onSuccess: tab.onSuccess ?? props.onSuccess,
|
|
237
|
+
onError: tab.onError ?? props.onError,
|
|
238
|
+
onCancel: tab.onCancel ?? props.onCancel,
|
|
239
|
+
onFinish: tab.onFinish ?? props.onFinish,
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const mappedItems = computed(() => {
|
|
243
|
+
const current = selectedTab.value;
|
|
244
|
+
const currentIndex = currentTabIndex.value;
|
|
245
|
+
const mapped = props.items.map((item) => {
|
|
246
|
+
const { key, label, eager } = item;
|
|
247
|
+
const tabId = `${instanceid}_tab_${item.key}`;
|
|
248
|
+
const panelId = `${instanceid}_tabpanel_${item.key}`;
|
|
249
|
+
const labelSlotName = `label-${item.key}` as `label-${K}`;
|
|
250
|
+
const active = item.key === selectedTab.value;
|
|
251
|
+
const tabClasses = {
|
|
252
|
+
'bb-tab__btn': true,
|
|
253
|
+
'bb-tab__btn--active': active,
|
|
254
|
+
};
|
|
255
|
+
const paneClasses = {
|
|
256
|
+
'bb-tab__pane': true,
|
|
257
|
+
'bb-tab__pane--shown': active,
|
|
258
|
+
};
|
|
259
|
+
const disabled = !!(item.disabled || props.disabled);
|
|
260
|
+
const ariaSelected = !!active;
|
|
261
|
+
const tabindex = active ? 0 : -1;
|
|
262
|
+
const handleClick = () => onTabSelected(item.key);
|
|
263
|
+
const handleKeydown = (event: KeyboardEvent) => onKeydown(event);
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
tabId: tabId,
|
|
267
|
+
eager: eager,
|
|
268
|
+
panelId: panelId,
|
|
269
|
+
active: active,
|
|
270
|
+
ariaSelected: ariaSelected,
|
|
271
|
+
labelSlotName: labelSlotName,
|
|
272
|
+
tabClasses: tabClasses,
|
|
273
|
+
paneClasses: paneClasses,
|
|
274
|
+
role: 'tab',
|
|
275
|
+
tabindex: tabindex,
|
|
276
|
+
disabled: disabled,
|
|
277
|
+
current: current,
|
|
278
|
+
key: key,
|
|
279
|
+
label: label,
|
|
280
|
+
currentIndex: currentIndex,
|
|
281
|
+
onClick: handleClick,
|
|
282
|
+
onKeydown: handleKeydown,
|
|
283
|
+
};
|
|
284
|
+
});
|
|
285
|
+
return mapped;
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
const onTabSelected = (tab: BbTabItem<K>['key'], replace = props.replace) => {
|
|
289
|
+
const tabItem = props.items.find((i) => i.key === tab);
|
|
290
|
+
if (isNil(tabItem)) return;
|
|
291
|
+
if (tabItem.key === selectedTab.value) return;
|
|
292
|
+
|
|
293
|
+
/* For Vue or Nuxt context */
|
|
294
|
+
if (props.navigation && route.value && queryTabParam.value !== tab) {
|
|
295
|
+
if (!router) return;
|
|
296
|
+
const method = replace ? 'replace' : 'push';
|
|
297
|
+
router[method](
|
|
298
|
+
merge(route.value, {
|
|
299
|
+
query: { [props.querykey]: tab },
|
|
300
|
+
replace: replace,
|
|
301
|
+
})
|
|
302
|
+
);
|
|
303
|
+
/* Inertia context — server visit (real round-trip, supports partial reloads) */
|
|
304
|
+
} else if (props.navigation && isInertia) {
|
|
305
|
+
const urlString = tabItem.href ?? globalThis.location.href;
|
|
306
|
+
const url = new URL(urlString, window.location.origin);
|
|
307
|
+
url.searchParams.set(props.querykey, tab);
|
|
308
|
+
|
|
309
|
+
if (props.server || tabItem.server) {
|
|
310
|
+
const base = instance?.proxy?.$inertia as any;
|
|
311
|
+
const merged = mergeInertiaOptions(tabItem);
|
|
312
|
+
const valid = Object.fromEntries(
|
|
313
|
+
Object.entries(merged).filter(([_key, value]) => isNotNil(value))
|
|
314
|
+
);
|
|
315
|
+
base.visit(url.toString(), valid);
|
|
316
|
+
} else {
|
|
317
|
+
// Client-only URL update — no server round-trip
|
|
318
|
+
if (replace) {
|
|
319
|
+
window.history.replaceState(window.history.state, '', url.toString());
|
|
320
|
+
} else {
|
|
321
|
+
window.history.pushState(window.history.state, '', url.toString());
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
if (props.modelValue !== tab) {
|
|
326
|
+
emit('update:modelValue', tab);
|
|
327
|
+
}
|
|
328
|
+
selectedTab.value = tab;
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
if (selectedTab.value) {
|
|
332
|
+
onTabSelected(selectedTab.value, true);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const goTo = (target: 'first' | 'previous' | 'next' | 'last') => {
|
|
336
|
+
var nextTabIndex: number | null = null;
|
|
337
|
+
switch (target) {
|
|
338
|
+
case 'first':
|
|
339
|
+
nextTabIndex = 0;
|
|
340
|
+
while (nextTabIndex < props.items.length - 1) {
|
|
341
|
+
if (props.items[nextTabIndex].disabled) {
|
|
342
|
+
nextTabIndex++;
|
|
343
|
+
} else break;
|
|
344
|
+
}
|
|
345
|
+
break;
|
|
346
|
+
case 'previous': {
|
|
347
|
+
let offset = 1;
|
|
348
|
+
while (offset < props.items.length) {
|
|
349
|
+
nextTabIndex =
|
|
350
|
+
(currentTabIndex.value + props.items.length - offset) %
|
|
351
|
+
props.items.length;
|
|
352
|
+
if (props.items[nextTabIndex].disabled) {
|
|
353
|
+
offset++;
|
|
354
|
+
} else break;
|
|
355
|
+
}
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
case 'next': {
|
|
359
|
+
let offset = 1;
|
|
360
|
+
while (offset < props.items.length) {
|
|
361
|
+
nextTabIndex = (currentTabIndex.value + offset) % props.items.length;
|
|
362
|
+
if (props.items[nextTabIndex].disabled) {
|
|
363
|
+
offset++;
|
|
364
|
+
} else break;
|
|
365
|
+
}
|
|
366
|
+
break;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
case 'last':
|
|
370
|
+
nextTabIndex = props.items.length - 1;
|
|
371
|
+
while (nextTabIndex >= 0) {
|
|
372
|
+
if (props.items[nextTabIndex].disabled) {
|
|
373
|
+
nextTabIndex--;
|
|
374
|
+
} else break;
|
|
375
|
+
}
|
|
376
|
+
break;
|
|
377
|
+
default:
|
|
378
|
+
break;
|
|
379
|
+
}
|
|
380
|
+
if (nextTabIndex === null) {
|
|
381
|
+
throw new Error('Could not navigate to next tab');
|
|
382
|
+
}
|
|
383
|
+
if (props.items[nextTabIndex].disabled) return;
|
|
384
|
+
const targetKey = props.items[nextTabIndex].key;
|
|
385
|
+
const button: HTMLButtonElement | null = document.querySelector(
|
|
386
|
+
`#${instanceid}_tab_${targetKey}`
|
|
387
|
+
);
|
|
388
|
+
button?.focus();
|
|
389
|
+
onTabSelected(targetKey);
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
const onKeydown = (event: KeyboardEvent) => {
|
|
393
|
+
if (props.disabled) return;
|
|
394
|
+
if (event.key === 'ArrowRight') {
|
|
395
|
+
goTo('next');
|
|
396
|
+
} else if (event.key === 'ArrowLeft') {
|
|
397
|
+
goTo('previous');
|
|
398
|
+
} else if (event.key === 'Home') {
|
|
399
|
+
event.preventDefault();
|
|
400
|
+
goTo('first');
|
|
401
|
+
} else if (event.key === 'End') {
|
|
402
|
+
event.preventDefault();
|
|
403
|
+
goTo('last');
|
|
404
|
+
} else {
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
watch(
|
|
410
|
+
() => props.modelValue,
|
|
411
|
+
(value) => {
|
|
412
|
+
if (value) {
|
|
413
|
+
onTabSelected(value as K);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
);
|
|
417
|
+
|
|
418
|
+
watch(
|
|
419
|
+
() => route.value?.query?.[props.querykey],
|
|
420
|
+
(value) => {
|
|
421
|
+
if (value && typeof value === 'string') {
|
|
422
|
+
onTabSelected(value as K);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
);
|
|
426
|
+
|
|
427
|
+
/** Scrolls the active tab label into view horizontally without affecting vertical scroll. */
|
|
428
|
+
watch(
|
|
429
|
+
selectedTab,
|
|
430
|
+
async () => {
|
|
431
|
+
if (!tablist.value) return;
|
|
432
|
+
const element = tablist.value.querySelector<HTMLElement>(
|
|
433
|
+
'.bb-tab__btn--active'
|
|
434
|
+
);
|
|
435
|
+
if (!element) return;
|
|
436
|
+
await nextTick();
|
|
437
|
+
|
|
438
|
+
const container = tablist.value;
|
|
439
|
+
const elLeft = element.offsetLeft;
|
|
440
|
+
const elRight = elLeft + element.offsetWidth;
|
|
441
|
+
const scrollLeft = container.scrollLeft;
|
|
442
|
+
const visibleRight = scrollLeft + container.clientWidth;
|
|
443
|
+
|
|
444
|
+
if (elLeft < scrollLeft) {
|
|
445
|
+
container.scrollTo({ left: elLeft, behavior: 'smooth' });
|
|
446
|
+
} else if (elRight > visibleRight) {
|
|
447
|
+
container.scrollTo({
|
|
448
|
+
left: elRight - container.clientWidth,
|
|
449
|
+
behavior: 'smooth',
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
},
|
|
453
|
+
{ flush: 'post' }
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
const animationDirection = ref<'left' | 'right'>('right');
|
|
457
|
+
watch(
|
|
458
|
+
selectedTab,
|
|
459
|
+
(current, previous) => {
|
|
460
|
+
if (current && previous) {
|
|
461
|
+
const indexCurrent = props.items.findIndex((i) => i.key === current);
|
|
462
|
+
const indexPrevious = props.items.findIndex((i) => i.key === previous);
|
|
463
|
+
animationDirection.value =
|
|
464
|
+
indexCurrent > indexPrevious ? 'right' : 'left';
|
|
465
|
+
}
|
|
466
|
+
},
|
|
467
|
+
{ flush: 'sync' }
|
|
468
|
+
);
|
|
469
|
+
|
|
470
|
+
const highlightWidth = ref(0);
|
|
471
|
+
const highlightHeight = ref(0);
|
|
472
|
+
const highlightLeft = ref(0);
|
|
473
|
+
const highlightTop = ref(0);
|
|
474
|
+
const containerWidth = ref(0);
|
|
475
|
+
const highlightReady = ref(false);
|
|
476
|
+
const getHighlightData = () => {
|
|
477
|
+
if (!tablist.value) {
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
const activeBtn = tablist.value.querySelector<HTMLElement>(
|
|
481
|
+
'.bb-tab__btn--active'
|
|
482
|
+
);
|
|
483
|
+
if (!activeBtn) {
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
highlightLeft.value = activeBtn.offsetLeft;
|
|
487
|
+
highlightTop.value = activeBtn.offsetTop;
|
|
488
|
+
highlightWidth.value = activeBtn.clientWidth;
|
|
489
|
+
highlightHeight.value = activeBtn.clientHeight;
|
|
490
|
+
containerWidth.value = tablist.value.scrollWidth;
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
onMounted(async () => {
|
|
494
|
+
getHighlightData();
|
|
495
|
+
await nextTick();
|
|
496
|
+
highlightReady.value = true;
|
|
497
|
+
});
|
|
498
|
+
watch(selectedTab, getHighlightData, { flush: 'post' });
|
|
499
|
+
useResizeObserver(tablist, () => {
|
|
500
|
+
getHighlightData();
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
watch(
|
|
504
|
+
() => hash(props.items),
|
|
505
|
+
() => {
|
|
506
|
+
if (
|
|
507
|
+
selectedTab.value &&
|
|
508
|
+
!props.items.find((i) => i.key === selectedTab.value)
|
|
509
|
+
) {
|
|
510
|
+
getInitialPosition();
|
|
511
|
+
}
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
immediate: true,
|
|
515
|
+
deep: true,
|
|
516
|
+
}
|
|
517
|
+
);
|
|
518
|
+
|
|
519
|
+
defineExpose({
|
|
520
|
+
isFirst,
|
|
521
|
+
isLast,
|
|
522
|
+
current: selectedTab,
|
|
523
|
+
currentIndex: currentTabIndex,
|
|
524
|
+
goTo,
|
|
525
|
+
});
|
|
526
|
+
</script>
|
|
527
|
+
|
|
528
|
+
<style lang="postcss">
|
|
529
|
+
@import './index.css';
|
|
530
|
+
</style>
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
## Types
|
|
534
|
+
|
|
535
|
+
```ts
|
|
536
|
+
import type { CommonProps } from '@/types/CommonProps';
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Inertia-specific options shared between `BbTabProps` (component-level defaults)
|
|
540
|
+
* and `BbTabItem` (per-item overrides). Item values always win over component values.
|
|
541
|
+
*/
|
|
542
|
+
export type BbTabInertiaOptions = {
|
|
543
|
+
/**
|
|
544
|
+
* Additional HTTP headers forwarded to Inertia when navigation is triggered.
|
|
545
|
+
* Only meaningful in an Inertia context.
|
|
546
|
+
*/
|
|
547
|
+
headers?: Record<string, string>;
|
|
548
|
+
/**
|
|
549
|
+
* Limits which server-side props are refreshed during a partial Inertia reload.
|
|
550
|
+
* Only meaningful in an Inertia context.
|
|
551
|
+
*/
|
|
552
|
+
only?: string[];
|
|
553
|
+
/**
|
|
554
|
+
* Inertia lifecycle hook — fired just before the request is sent.
|
|
555
|
+
* Return `false` to cancel the visit.
|
|
556
|
+
*/
|
|
557
|
+
onBefore?: () => boolean | void;
|
|
558
|
+
/**
|
|
559
|
+
* Inertia lifecycle hook — fired when the request is cancelled.
|
|
560
|
+
*/
|
|
561
|
+
onCancel?: () => void;
|
|
562
|
+
/**
|
|
563
|
+
* Inertia lifecycle hook — fired after the visit finishes (success or error).
|
|
564
|
+
*/
|
|
565
|
+
onFinish?: () => void;
|
|
566
|
+
/**
|
|
567
|
+
* Inertia lifecycle hook — fired when the server returns validation errors.
|
|
568
|
+
*/
|
|
569
|
+
onError?: (errors: Record<string, string>) => void;
|
|
570
|
+
/**
|
|
571
|
+
* Inertia lifecycle hook — fired when upload/download progress is available.
|
|
572
|
+
*/
|
|
573
|
+
onProgress?: (progress: { percentage: number | undefined }) => void;
|
|
574
|
+
/**
|
|
575
|
+
* Inertia lifecycle hook — fired when the request starts.
|
|
576
|
+
*/
|
|
577
|
+
onStart?: () => void;
|
|
578
|
+
/**
|
|
579
|
+
* Inertia lifecycle hook — fired when the visit completes successfully.
|
|
580
|
+
*/
|
|
581
|
+
onSuccess?: () => void;
|
|
582
|
+
/**
|
|
583
|
+
* Controls whether Inertia preserves the scroll position after navigation.
|
|
584
|
+
* Only meaningful in an Inertia context.
|
|
585
|
+
*/
|
|
586
|
+
preserveScroll?: boolean | ((props: Record<string, unknown>) => boolean);
|
|
587
|
+
/**
|
|
588
|
+
* Controls whether Inertia preserves the current component state after navigation.
|
|
589
|
+
* Only meaningful in an Inertia context.
|
|
590
|
+
*/
|
|
591
|
+
preserveState?:
|
|
592
|
+
| boolean
|
|
593
|
+
| ((props: Record<string, unknown>) => boolean)
|
|
594
|
+
| null;
|
|
595
|
+
/**
|
|
596
|
+
* Uses history replacement instead of push navigation.
|
|
597
|
+
*/
|
|
598
|
+
replace?: boolean;
|
|
599
|
+
};
|
|
600
|
+
|
|
601
|
+
export type BbTabEvents = {
|
|
602
|
+
/**
|
|
603
|
+
* Emitted when the selected tab changes. Used by `v-model`.
|
|
604
|
+
*/
|
|
605
|
+
(e: 'update:modelValue', value: string): void;
|
|
606
|
+
};
|
|
607
|
+
|
|
608
|
+
export type BbTabItem<K extends string = string> = Pick<
|
|
609
|
+
CommonProps,
|
|
610
|
+
'disabled'
|
|
611
|
+
> &
|
|
612
|
+
BbTabInertiaOptions & {
|
|
613
|
+
/**
|
|
614
|
+
* Used to render a tab even when not visible.
|
|
615
|
+
* Useful for SEO purposes — the tab panel content is in the DOM even when the
|
|
616
|
+
* tab is inactive.
|
|
617
|
+
*/
|
|
618
|
+
eager?: boolean;
|
|
619
|
+
/**
|
|
620
|
+
* Destination URL for this tab. In an Inertia context the tab button renders
|
|
621
|
+
* as an Inertia Link that navigates to this URL on click.
|
|
622
|
+
*/
|
|
623
|
+
href?: string;
|
|
624
|
+
/**
|
|
625
|
+
* String that identifies the tab.
|
|
626
|
+
*/
|
|
627
|
+
key: K;
|
|
628
|
+
/**
|
|
629
|
+
* Label to use as the tab text.
|
|
630
|
+
*/
|
|
631
|
+
label?: string;
|
|
632
|
+
/**
|
|
633
|
+
* When true and in an Inertia context, triggers a real server-side Inertia
|
|
634
|
+
* visit (via `router.visit`) instead of a client-only `history.pushState`.
|
|
635
|
+
* All other Inertia options and lifecycle hooks are forwarded to that visit.
|
|
636
|
+
*/
|
|
637
|
+
server?: boolean;
|
|
638
|
+
};
|
|
639
|
+
|
|
640
|
+
export type BbTabProps<K extends string = string> = Pick<
|
|
641
|
+
CommonProps,
|
|
642
|
+
'disabled' | 'eager' | 'id'
|
|
643
|
+
> &
|
|
644
|
+
BbTabInertiaOptions & {
|
|
645
|
+
/**
|
|
646
|
+
* Enables horizontal slide transitions between tab panels.
|
|
647
|
+
*
|
|
648
|
+
* @defaultValue `true`
|
|
649
|
+
*/
|
|
650
|
+
// eslint-disable-next-line vue/prop-name-casing
|
|
651
|
+
animateX?: boolean;
|
|
652
|
+
/**
|
|
653
|
+
* Enables smooth height animation when switching between tab panels.
|
|
654
|
+
*
|
|
655
|
+
* @defaultValue `true`
|
|
656
|
+
*/
|
|
657
|
+
// eslint-disable-next-line vue/prop-name-casing
|
|
658
|
+
animateY?: boolean;
|
|
659
|
+
/**
|
|
660
|
+
* Direction of the tabs component.
|
|
661
|
+
*
|
|
662
|
+
* @defaultValue `'horizontal'`
|
|
663
|
+
*/
|
|
664
|
+
direction?: 'horizontal' | 'vertical';
|
|
665
|
+
/**
|
|
666
|
+
* Array of items that define the tabs in the component.
|
|
667
|
+
*/
|
|
668
|
+
items: BbTabItem<K>[];
|
|
669
|
+
/**
|
|
670
|
+
* The current tab key. Used by v-model.
|
|
671
|
+
*/
|
|
672
|
+
modelValue?: null | BbTabItem<K>['key'];
|
|
673
|
+
/**
|
|
674
|
+
* Synchronizes the current tab with the URL query parameter.
|
|
675
|
+
*/
|
|
676
|
+
navigation?: boolean;
|
|
677
|
+
/**
|
|
678
|
+
* Defines the query key used for URL-based navigation.
|
|
679
|
+
*
|
|
680
|
+
* @defaultValue `'tab'`
|
|
681
|
+
*/
|
|
682
|
+
querykey?: string;
|
|
683
|
+
/**
|
|
684
|
+
* Replaces history rather than pushing it when changing tab.
|
|
685
|
+
*/
|
|
686
|
+
replace?: boolean;
|
|
687
|
+
/**
|
|
688
|
+
* When true and in an Inertia context, triggers a real server-side Inertia
|
|
689
|
+
* visit instead of a client-only `history.pushState`.
|
|
690
|
+
*/
|
|
691
|
+
server?: boolean;
|
|
692
|
+
};
|
|
693
|
+
|
|
694
|
+
/** Props exposed by the per-tab label slots and the generic `label` fallback slot. */
|
|
695
|
+
export type BbTabSlotProps<K extends string = string> = {
|
|
696
|
+
/** The key of the currently active tab. */
|
|
697
|
+
current: string | null;
|
|
698
|
+
/** Zero-based index of the currently active tab. */
|
|
699
|
+
currentIndex: number;
|
|
700
|
+
/** Whether this tab is disabled. */
|
|
701
|
+
disabled: boolean;
|
|
702
|
+
/** Whether the active tab is the first tab. */
|
|
703
|
+
isFirst: boolean;
|
|
704
|
+
/** Whether the active tab is the last tab. */
|
|
705
|
+
isLast: boolean;
|
|
706
|
+
/** Whether this specific tab is currently active. */
|
|
707
|
+
selected: boolean;
|
|
708
|
+
/** The `label` value of this tab item. */
|
|
709
|
+
text: BbTabItem<K>['label'];
|
|
710
|
+
/** The `key` value of this tab item. */
|
|
711
|
+
value: string;
|
|
712
|
+
/** Navigate to `'first'`, `'previous'`, `'next'`, or `'last'` tab. */
|
|
713
|
+
goTo: (target: 'first' | 'previous' | 'next' | 'last') => void;
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
/** Props exposed by the `header:prepend` and `header:append` slots. */
|
|
717
|
+
export type BbTabScrollSlotProps = {
|
|
718
|
+
/** Whether the tab list is currently being scrolled. */
|
|
719
|
+
isScrolling: boolean;
|
|
720
|
+
/** Whether the tab list has reached the left edge. */
|
|
721
|
+
left: boolean;
|
|
722
|
+
/** Whether the tab list has reached the right edge. */
|
|
723
|
+
right: boolean;
|
|
724
|
+
};
|
|
725
|
+
|
|
726
|
+
export type BbTabSlots<K extends string = string> = {
|
|
727
|
+
/**
|
|
728
|
+
* Content rendered before the scrollable tab label list (e.g. a left scroll arrow).
|
|
729
|
+
*/
|
|
730
|
+
'header:prepend'?: (props: BbTabScrollSlotProps) => any;
|
|
731
|
+
/**
|
|
732
|
+
* Content rendered after the scrollable tab label list (e.g. a right scroll arrow).
|
|
733
|
+
*/
|
|
734
|
+
'header:append'?: (props: BbTabScrollSlotProps) => any;
|
|
735
|
+
/**
|
|
736
|
+
* Fallback slot used for all tab labels when no per-tab `label-{key}` slot is provided.
|
|
737
|
+
*/
|
|
738
|
+
label?: (props: BbTabSlotProps<K>) => any;
|
|
739
|
+
} & Partial<Record<K | `label-${K}`, (props: BbTabSlotProps<K>) => any>>;
|
|
740
|
+
```
|
|
741
|
+
|
|
742
|
+
## Styles
|
|
743
|
+
|
|
744
|
+
```css
|
|
745
|
+
.bb-tab {
|
|
746
|
+
--transition-duration: 350ms;
|
|
747
|
+
--gap: 30px;
|
|
748
|
+
color: var(--bb-text);
|
|
749
|
+
display: grid;
|
|
750
|
+
gap: 24px 24px;
|
|
751
|
+
|
|
752
|
+
&--horizontal {
|
|
753
|
+
> .bb-tab__label-boundary {
|
|
754
|
+
&::before {
|
|
755
|
+
bottom: 2px;
|
|
756
|
+
height: 1px;
|
|
757
|
+
left: 0px;
|
|
758
|
+
width: 100%;
|
|
759
|
+
}
|
|
760
|
+
.bb-tab__label-container {
|
|
761
|
+
padding: var(--bb-ring-size) var(--bb-ring-size) 12px
|
|
762
|
+
var(--bb-ring-size);
|
|
763
|
+
white-space: nowrap;
|
|
764
|
+
|
|
765
|
+
&::before {
|
|
766
|
+
bottom: 0px;
|
|
767
|
+
height: 5px;
|
|
768
|
+
left: 0px;
|
|
769
|
+
translate: var(--left) 0px;
|
|
770
|
+
width: var(--width);
|
|
771
|
+
}
|
|
772
|
+
> li + li {
|
|
773
|
+
margin-left: 40px;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
&--vertical {
|
|
780
|
+
display: grid;
|
|
781
|
+
grid-template-columns: auto 1fr;
|
|
782
|
+
|
|
783
|
+
> .bb-tab__label-boundary {
|
|
784
|
+
&::before {
|
|
785
|
+
height: 100%;
|
|
786
|
+
right: 2px;
|
|
787
|
+
top: 0px;
|
|
788
|
+
width: 1px;
|
|
789
|
+
}
|
|
790
|
+
.bb-tab__label-container {
|
|
791
|
+
align-items: stretch;
|
|
792
|
+
display: flex;
|
|
793
|
+
flex-direction: column;
|
|
794
|
+
padding: var(--bb-ring-size) 12px var(--bb-ring-size)
|
|
795
|
+
var(--bb-ring-size);
|
|
796
|
+
|
|
797
|
+
> li + li {
|
|
798
|
+
margin-top: 10px;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
&::before {
|
|
802
|
+
height: var(--height);
|
|
803
|
+
right: 0px;
|
|
804
|
+
top: 0px;
|
|
805
|
+
translate: 0px var(--top);
|
|
806
|
+
width: 5px;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
.bb-tab__btn {
|
|
810
|
+
text-align: left;
|
|
811
|
+
width: 100%;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
> span {
|
|
817
|
+
display: block;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
.bb-tab__label-boundary {
|
|
822
|
+
display: grid;
|
|
823
|
+
grid-template-columns: auto 1fr auto;
|
|
824
|
+
position: relative;
|
|
825
|
+
|
|
826
|
+
&::before {
|
|
827
|
+
background-color: var(--bb-border);
|
|
828
|
+
content: '';
|
|
829
|
+
display: block;
|
|
830
|
+
position: absolute;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
.bb-tab__label-container {
|
|
834
|
+
font-weight: 500;
|
|
835
|
+
list-style: none;
|
|
836
|
+
|
|
837
|
+
-ms-overflow-style: none; /* IE and Edge */
|
|
838
|
+
overflow-x: auto;
|
|
839
|
+
position: relative;
|
|
840
|
+
scrollbar-width: none; /* Firefox */
|
|
841
|
+
|
|
842
|
+
&::-webkit-scrollbar {
|
|
843
|
+
display: none;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
&::before {
|
|
847
|
+
background-color: var(--bb-primary);
|
|
848
|
+
border-radius: var(--bb-radius);
|
|
849
|
+
content: '';
|
|
850
|
+
position: absolute;
|
|
851
|
+
transition:
|
|
852
|
+
translate 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
|
853
|
+
width 0.25s cubic-bezier(0.4, 0, 0.2, 1),
|
|
854
|
+
height 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
|
855
|
+
}
|
|
856
|
+
&--no-transition::before {
|
|
857
|
+
display: none;
|
|
858
|
+
}
|
|
859
|
+
li {
|
|
860
|
+
display: inline-block;
|
|
861
|
+
.bb-tab__btn {
|
|
862
|
+
min-height: 40px;
|
|
863
|
+
scroll-margin: 60px;
|
|
864
|
+
|
|
865
|
+
&--active {
|
|
866
|
+
color: var(--bb-primary);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
.bb-tab__panes-container {
|
|
874
|
+
.bb-smooth-height {
|
|
875
|
+
> .content {
|
|
876
|
+
display: grid;
|
|
877
|
+
grid-template-columns: 1fr;
|
|
878
|
+
position: relative;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
> div:not(.bb-smooth-height) {
|
|
883
|
+
display: grid;
|
|
884
|
+
grid-template-columns: 1fr;
|
|
885
|
+
position: relative;
|
|
886
|
+
overflow: clip;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
.tab-slide-left-enter-active,
|
|
890
|
+
.tab-slide-right-enter-active {
|
|
891
|
+
transition:
|
|
892
|
+
translate var(--transition-duration) cubic-bezier(0.2, 0, 0, 1),
|
|
893
|
+
opacity calc(var(--transition-duration) * 0.6)
|
|
894
|
+
cubic-bezier(0, 0, 0.2, 1);
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
.tab-slide-left-leave-active,
|
|
898
|
+
.tab-slide-right-leave-active {
|
|
899
|
+
transition:
|
|
900
|
+
translate calc(var(--transition-duration) * 0.8)
|
|
901
|
+
cubic-bezier(0.4, 0, 1, 1),
|
|
902
|
+
opacity calc(var(--transition-duration) * 0.4)
|
|
903
|
+
cubic-bezier(0.4, 0, 1, 1);
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
.tab-slide-left-enter-from {
|
|
907
|
+
opacity: 0;
|
|
908
|
+
translate: calc(-100% - var(--gap)) 0px;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
.tab-slide-left-leave-to {
|
|
912
|
+
opacity: 0;
|
|
913
|
+
translate: calc(100% + var(--gap)) 0px;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
.tab-slide-left-enter-to,
|
|
917
|
+
.tab-slide-left-leave-from,
|
|
918
|
+
.tab-slide-right-enter-to,
|
|
919
|
+
.tab-slide-right-leave-from {
|
|
920
|
+
opacity: 1;
|
|
921
|
+
translate: 0px 0px;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
.tab-slide-right-enter-from {
|
|
925
|
+
opacity: 0;
|
|
926
|
+
translate: calc(100% + var(--gap)) 0px;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
.tab-slide-right-leave-to {
|
|
930
|
+
opacity: 0;
|
|
931
|
+
translate: calc(-100% - var(--gap)) 0px;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
@media (prefers-reduced-motion: reduce) {
|
|
935
|
+
.tab-slide-left-enter-active,
|
|
936
|
+
.tab-slide-left-leave-active,
|
|
937
|
+
.tab-slide-right-enter-active,
|
|
938
|
+
.tab-slide-right-leave-active {
|
|
939
|
+
transition: none;
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
.bb-tab__pane {
|
|
944
|
+
display: none;
|
|
945
|
+
grid-column: 1 / 2;
|
|
946
|
+
grid-row: 1 / 2;
|
|
947
|
+
|
|
948
|
+
&--shown {
|
|
949
|
+
display: block;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
```
|