diff --git a/frontend/src/components/SwipeTabs.tsx b/frontend/src/components/SwipeTabs.tsx index ae89b8f..30e3de9 100644 --- a/frontend/src/components/SwipeTabs.tsx +++ b/frontend/src/components/SwipeTabs.tsx @@ -144,6 +144,10 @@ export function SwipeTabs({ setContainerHeight((prev) => (prev === nextHeight ? prev : nextHeight)); }, []); + const handleTabsMetrics = useCallback(() => { + updateHeight(); + }, [updateHeight]); + const measureWidth = useCallback(() => { const width = containerRef.current?.getBoundingClientRect().width || 0; if (width && width !== widthRef.current) { @@ -192,6 +196,14 @@ export function SwipeTabs({ }; }, [displayIndex, updateHeight]); + useEffect(() => { + if (typeof window === 'undefined') return undefined; + window.addEventListener('tabs:metrics', handleTabsMetrics); + return () => { + window.removeEventListener('tabs:metrics', handleTabsMetrics); + }; + }, [handleTabsMetrics]); + useEffect(() => { return () => { if (rafRef.current) { diff --git a/frontend/src/components/TabsScroller.tsx b/frontend/src/components/TabsScroller.tsx index 3b545cd..10f509c 100644 --- a/frontend/src/components/TabsScroller.tsx +++ b/frontend/src/components/TabsScroller.tsx @@ -42,6 +42,7 @@ const TabsScroller = forwardRef( const [showRight, setShowRight] = useState(false); const heightRef = useRef(0); const pillHeightRef = useRef(0); + const sliderHeightRef = useRef(0); const dragRef = useRef({ pointerId: null as number | null, @@ -76,12 +77,27 @@ const TabsScroller = forwardRef( node.dataset.hasActions = hasActionsValue; const container = node.closest('.page-tabs-container, .admin-tabs-container'); const mainContent = node.closest('.main-content'); - let containerHeight = 0; if (container instanceof HTMLElement) { container.dataset.hasTabs = hasTabsValue; container.dataset.hasActions = hasActionsValue; + } + const sliderHeight = Math.ceil(node.getBoundingClientRect().height); + let containerHeight = 0; + if (container instanceof HTMLElement) { containerHeight = Math.ceil(container.getBoundingClientRect().height); } + if (sliderHeight && sliderHeight !== sliderHeightRef.current) { + sliderHeightRef.current = sliderHeight; + if (typeof document !== 'undefined') { + document.documentElement.style.setProperty('--tabs-slider-height', `${sliderHeight}px`); + } + if (mainContent instanceof HTMLElement) { + mainContent.style.setProperty('--tabs-slider-height', `${sliderHeight}px`); + } + if (typeof window !== 'undefined') { + window.dispatchEvent(new Event('tabs:metrics')); + } + } const firstTabButton = node.querySelector('.page-tab-btn, .admin-tab-btn'); if (firstTabButton instanceof HTMLElement) { const pillHeight = Math.ceil(firstTabButton.getBoundingClientRect().height); @@ -133,6 +149,7 @@ const TabsScroller = forwardRef( useLayoutEffect(() => { updateOverflow(); + scheduleOverflowUpdate(); const node = sliderRef.current; if (!node) return undefined; const container = node.closest('.page-tabs-container, .admin-tabs-container'); diff --git a/frontend/src/styles/Layout.css b/frontend/src/styles/Layout.css index ceaa6c7..52bb5fa 100644 --- a/frontend/src/styles/Layout.css +++ b/frontend/src/styles/Layout.css @@ -444,6 +444,9 @@ label, @media (max-width: 768px) { .main-content { margin-left: 0; + --page-padding-y-mobile-gap: var(--space-3); + --page-padding-y-mobile-bottom-gap: 0; + --tabs-slider-height-fallback: calc(var(--tab-pill-height, 44px) + 8px); } /* Override collapsed state margin on mobile */ @@ -564,27 +567,19 @@ label, display: none; } - .page-content { - padding: var(--page-padding-y-mobile) var(--page-padding-x-mobile); - padding-top: var(--title-bar-offset, var(--title-bar-height, 0px)); - } - + .page-content, .admin-tab-content { padding: var(--page-padding-y-mobile) var(--page-padding-x-mobile); - padding-top: var(--title-bar-offset, var(--title-bar-height, 0px)); + padding-top: calc(var(--title-bar-offset, 0px) + var(--page-padding-y-mobile-gap, var(--page-padding-y-mobile))); } } /* Small mobile - further reduce spacing */ @media (max-width: 480px) { - .page-content { - padding: var(--page-padding-y-mobile) var(--page-padding-x-mobile); - padding-top: var(--title-bar-offset, var(--title-bar-height, 0px)); - } - + .page-content, .admin-tab-content { padding: var(--page-padding-y-mobile) var(--page-padding-x-mobile); - padding-top: var(--title-bar-offset, var(--title-bar-height, 0px)); + padding-top: calc(var(--title-bar-offset, 0px) + var(--page-padding-y-mobile-gap, var(--page-padding-y-mobile))); } } @@ -840,8 +835,9 @@ label, [data-tab-position='top'] .main-content[data-has-tabs='true'] .admin-tab-content, [data-tab-position='top'] .main-content[data-has-actions='true'] .admin-tab-content { padding-top: calc( - var(--title-bar-offset, var(--title-bar-height, 0px)) + - var(--tabs-bar-height, 72px) + var(--title-bar-offset, 0px) + + var(--tabs-bar-height, 72px) + + var(--page-padding-y-mobile-gap, var(--page-padding-y-mobile)) ); } } @@ -893,13 +889,11 @@ label, padding-bottom: 0; } -[data-tab-position='bottom'] .main-content[data-has-tabs='true'] .page-content, -[data-tab-position='bottom'] .main-content[data-has-tabs='true'] .admin-tab-swipe { +[data-tab-position='bottom'] .main-content[data-has-tabs='true'] .page-content { padding-top: var(--page-padding-y); } [data-tab-position='bottom'] .main-content[data-has-tabs='true'] .page-content, -[data-tab-position='bottom'] .main-content[data-has-tabs='true'] .admin-tab-swipe, [data-tab-position='bottom'] .main-content[data-has-tabs='true'] .admin-tab-content { padding-bottom: calc( var(--page-padding-y) + @@ -929,17 +923,16 @@ label, } /* Mobile padding for bottom position */ - [data-tab-position='bottom'] .main-content[data-has-tabs='true'] .page-content, - [data-tab-position='bottom'] .main-content[data-has-tabs='true'] .admin-tab-swipe { - padding-top: var(--title-bar-offset, var(--title-bar-height, 0px)); + [data-tab-position='bottom'] .main-content[data-has-tabs='true'] .page-content { + padding-top: calc(var(--title-bar-offset, 0px) + var(--page-padding-y-mobile-gap, var(--page-padding-y-mobile))); } [data-tab-position='bottom'] .main-content[data-has-tabs='true'] .page-content, - [data-tab-position='bottom'] .main-content[data-has-tabs='true'] .admin-tab-swipe, [data-tab-position='bottom'] .main-content[data-has-tabs='true'] .admin-tab-content { padding-bottom: calc( - var(--page-padding-y-mobile) + - var(--tabs-bar-height, calc(72px + env(safe-area-inset-bottom, 0px))) + var(--page-padding-y-mobile-bottom-gap, 0px) + + var(--tabs-slider-height, var(--tabs-slider-height-fallback, 52px)) + + env(safe-area-inset-bottom, 0px) ); } } @@ -971,17 +964,16 @@ label, padding-bottom: 0; } - [data-tab-position='responsive'] .main-content[data-has-tabs='true'] .page-content, - [data-tab-position='responsive'] .main-content[data-has-tabs='true'] .admin-tab-swipe { - padding-top: var(--title-bar-offset, var(--title-bar-height, 0px)); + [data-tab-position='responsive'] .main-content[data-has-tabs='true'] .page-content { + padding-top: calc(var(--title-bar-offset, 0px) + var(--page-padding-y-mobile-gap, var(--page-padding-y-mobile))); } [data-tab-position='responsive'] .main-content[data-has-tabs='true'] .page-content, - [data-tab-position='responsive'] .main-content[data-has-tabs='true'] .admin-tab-swipe, [data-tab-position='responsive'] .main-content[data-has-tabs='true'] .admin-tab-content { padding-bottom: calc( - var(--page-padding-y-mobile) + - var(--tabs-bar-height, calc(72px + env(safe-area-inset-bottom, 0px))) + var(--page-padding-y-mobile-bottom-gap, 0px) + + var(--tabs-slider-height, var(--tabs-slider-height-fallback, 52px)) + + env(safe-area-inset-bottom, 0px) ); } } diff --git a/frontend/src/styles/ThemeSettings.css b/frontend/src/styles/ThemeSettings.css index 5486ee8..a45af8e 100644 --- a/frontend/src/styles/ThemeSettings.css +++ b/frontend/src/styles/ThemeSettings.css @@ -1269,6 +1269,10 @@ margin-bottom: 2rem; } + .theme-section:last-child { + margin-bottom: 0; + } + /* Appearance Grid Mobile Overrides */ .appearance-grid { gap: 2rem;