From 34a366c7052cdc34746c32ebd4e03bd1551dfb73 Mon Sep 17 00:00:00 2001 From: andrewwallacespeckle Date: Fri, 4 Jul 2025 13:10:16 +0200 Subject: [PATCH] fix(fe): underline position on tab resize in Horizontal tabs Replaces useElementSize with useResizeObserver to update the underline position and width when the active tab changes size, ensuring the underline stays correctly aligned even when tab content changes (e.g., badge count updates). --- .../src/components/layout/tabs/Horizontal.vue | 42 ++++++++++++------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/packages/ui-components/src/components/layout/tabs/Horizontal.vue b/packages/ui-components/src/components/layout/tabs/Horizontal.vue index e865e146b..486edd731 100644 --- a/packages/ui-components/src/components/layout/tabs/Horizontal.vue +++ b/packages/ui-components/src/components/layout/tabs/Horizontal.vue @@ -98,7 +98,7 @@ import { isClient } from '@vueuse/core' import { ArrowLongRightIcon, ArrowLongLeftIcon } from '@heroicons/vue/24/outline' import type { Nullable } from '@speckle/shared' import { throttle } from '#lodash' -import { useElementSize } from '@vueuse/core' +import { useResizeObserver } from '@vueuse/core' import CommonBadge from '~~/src/components/common/Badge.vue' const props = defineProps<{ @@ -113,7 +113,8 @@ const showLeftArrow = ref(false) const showRightArrow = ref(false) const isInitialSetup = ref(true) -const { width } = useElementSize(buttonContainer) +const underlineLeft = ref('0px') +const underlineWidth = ref('0px') const buttonClass = computed(() => { return (item: LayoutPageTabItem) => { @@ -152,18 +153,18 @@ const activeItemRef = computed(() => { return btns.find((b) => b.dataset['tabId'] === id) || null }) -const borderStyle = computed(() => { - // Using width in calculation to force dependency - return width.value - ? { - left: `${activeItemRef.value?.offsetLeft || 0}px`, - width: `${activeItemRef.value?.clientWidth || 0}px` - } - : { - left: '0px', - width: '0px' - } -}) +const borderStyle = computed(() => ({ + left: underlineLeft.value, + width: underlineWidth.value +})) + +const updateUnderline = () => { + const el = activeItemRef.value + if (!el) return + + underlineLeft.value = `${el.offsetLeft}px` + underlineWidth.value = `${el.clientWidth}px` +} const setActiveItem = (item: LayoutPageTabItem) => { activeItem.value = item @@ -232,6 +233,19 @@ watch( } ) +// React to size changes of the active tab (e.g. when count badge disappears) +watch( + () => activeItemRef.value, + (el, _prev, onCleanup) => { + if (!el) return + updateUnderline() + + const { stop } = useResizeObserver(el, updateUnderline) + onCleanup(stop) + }, + { immediate: true } +) + onBeforeUnmount(() => { handleScroll.cancel() })