diff --git a/packages/main/src/components/AnalyticalTable/AnalyticalTable.module.css b/packages/main/src/components/AnalyticalTable/AnalyticalTable.module.css index 82b8a23822b..5f5477a7399 100644 --- a/packages/main/src/components/AnalyticalTable/AnalyticalTable.module.css +++ b/packages/main/src/components/AnalyticalTable/AnalyticalTable.module.css @@ -661,15 +661,16 @@ } /* ========================================================================== - Firefox scrollbar styles + Native scrollbar styles ========================================================================== */ -.firefoxCell { +.nativeScrollbarCell { &:last-child { - padding-inline-end: 18px; + /* keep the cell's base inline padding and additionally reserve the scrollbar width */ + padding-inline-end: calc(0.5rem + var(--_ui5wcr-AnalyticalTable-ScrollbarWidth, 18px)); } } -.firefoxNativeScrollbar { +.nativeScrollbar { scrollbar-width: inherit; } diff --git a/packages/main/src/components/AnalyticalTable/TableBody/VirtualTableBodyContainer.tsx b/packages/main/src/components/AnalyticalTable/TableBody/VirtualTableBodyContainer.tsx index 11b17cdc822..081e0bd646d 100644 --- a/packages/main/src/components/AnalyticalTable/TableBody/VirtualTableBodyContainer.tsx +++ b/packages/main/src/components/AnalyticalTable/TableBody/VirtualTableBodyContainer.tsx @@ -21,7 +21,7 @@ interface VirtualTableBodyContainerProps { rowCollapsedFlag?: boolean; dispatch: (e: { type: string; payload?: any }) => void; isGrouped: boolean; - isFirefox: boolean; + nativeScrollbar: boolean; } export const VirtualTableBodyContainer = (props: VirtualTableBodyContainerProps) => { @@ -41,7 +41,7 @@ export const VirtualTableBodyContainer = (props: VirtualTableBodyContainerProps) popInRowHeight, rowCollapsedFlag, isGrouped, - isFirefox, + nativeScrollbar, dispatch, } = props; const [isMounted, setIsMounted] = useState(false); @@ -119,7 +119,7 @@ export const VirtualTableBodyContainer = (props: VirtualTableBodyContainerProps) return (
{ - // safe here because we only update state after mount for SSR hydration - // eslint-disable-next-line react-hooks/set-state-in-effect - setIsFirefox(isFirefoxFn()); - }, []); - - return isFirefox; -} diff --git a/packages/main/src/components/AnalyticalTable/hooks/useNativeScrollbar.ts b/packages/main/src/components/AnalyticalTable/hooks/useNativeScrollbar.ts new file mode 100644 index 00000000000..90edc2e5959 --- /dev/null +++ b/packages/main/src/components/AnalyticalTable/hooks/useNativeScrollbar.ts @@ -0,0 +1,43 @@ +// When reused, move to base pkg +import { isFirefox as isFirefoxFn } from '@ui5/webcomponents-react-base/Device'; +import { useEffect, useState } from 'react'; + +/** + * Creates an invisible element for measuring scrollbar width. + * + * __Note:__ Overlay scrollbars don't have a width. + */ +function getScrollbarWidth() { + const el = document.createElement('div'); + el.style.cssText = 'overflow:scroll;position:absolute;top:-9999px;width:100px;height:100px'; + document.body.appendChild(el); + const width = el.offsetWidth - el.clientWidth; + el.remove(); + return width; +} + +interface NativeScrollbarInfo { + nativeScrollbar: boolean; + scrollbarWidth: number; +} + +/** + * SSR ready hook that determines whether the table uses the browser's native scrollbar. + * + * The native scrollbar is used for overlay scrollbars and Firefox, otherwise the custom `VerticalScrollbar` replaces the classic scrollbar with the styled one. + * + * __Note:__ Measured once on mount, a mode change (OS setting or (dis)connecting a mouse) requires a page refresh. + */ +export function useNativeScrollbar(): NativeScrollbarInfo { + const [scrollbarWidth, setScrollbarWidth] = useState(0); + const [isFirefox, setIsFirefox] = useState(false); + + useEffect(() => { + // only update state after mount for SSR hydration + // eslint-disable-next-line react-hooks/set-state-in-effect + setIsFirefox(isFirefoxFn()); + setScrollbarWidth(getScrollbarWidth()); + }, []); + + return { nativeScrollbar: isFirefox || scrollbarWidth === 0, scrollbarWidth }; +} diff --git a/packages/main/src/components/AnalyticalTable/hooks/usePopIn.ts b/packages/main/src/components/AnalyticalTable/hooks/usePopIn.ts index d3ccf4e49f6..03b07e9a58e 100644 --- a/packages/main/src/components/AnalyticalTable/hooks/usePopIn.ts +++ b/packages/main/src/components/AnalyticalTable/hooks/usePopIn.ts @@ -7,11 +7,10 @@ const popInVisibleColumnsDeps: ReactTableHooks['visibleColumnsDeps'][0] = (deps, ]; const popInVisibleColumns: ReactTableHooks['visibleColumns'][0] = (cols, { instance }) => { - const { state, dispatch } = instance; + const { state, dispatch, webComponentsReactProperties } = instance; + const { scrollbarWidth } = webComponentsReactProperties; - const tableClientWidth = state.isScrollable - ? state?.tableClientWidth + 13 /*scrollbar width*/ - : state?.tableClientWidth; + const tableClientWidth = state.isScrollable ? state?.tableClientWidth + scrollbarWidth : state?.tableClientWidth; const popInColumns: AnalyticalTableState['popInColumns'] = cols .filter((item) => { diff --git a/packages/main/src/components/AnalyticalTable/hooks/useStyling.ts b/packages/main/src/components/AnalyticalTable/hooks/useStyling.ts index ccfb9f915e6..84ba356fb60 100644 --- a/packages/main/src/components/AnalyticalTable/hooks/useStyling.ts +++ b/packages/main/src/components/AnalyticalTable/hooks/useStyling.ts @@ -87,7 +87,7 @@ const getRowProps = ( const getCellProps = (cellProps, { cell: { column }, instance }) => { const { webComponentsReactProperties, state } = instance; - const { classes, isFirefox } = webComponentsReactProperties; + const { classes, nativeScrollbar } = webComponentsReactProperties; const style: CSSProperties = { width: `${column.totalWidth}px`, ...resolveCellAlignment(column) }; if ( @@ -105,7 +105,7 @@ const getCellProps = (cellProps, { cell: { column }, instance }) => { cellProps.className, classes.tableCell, column.className, - isFirefox && state.isScrollable && classes.firefoxCell, + nativeScrollbar && state.isScrollable && classes.nativeScrollbarCell, ), style, tabIndex: -1, diff --git a/packages/main/src/components/AnalyticalTable/index.tsx b/packages/main/src/components/AnalyticalTable/index.tsx index e8d52a2cb00..fb0fd8a9e86 100644 --- a/packages/main/src/components/AnalyticalTable/index.tsx +++ b/packages/main/src/components/AnalyticalTable/index.tsx @@ -59,8 +59,8 @@ import { useColumnsDeps } from './hooks/useColumnsDeps.js'; import { useColumnDragAndDrop } from './hooks/useDragAndDrop.js'; import { useDynamicColumnWidths } from './hooks/useDynamicColumnWidths.js'; import { useFontsReady } from './hooks/useFontsReady.js'; -import { useIsFirefox } from './hooks/useIsFirefox.js'; import { useKeyboardNavigation } from './hooks/useKeyboardNavigation.js'; +import { useNativeScrollbar } from './hooks/useNativeScrollbar.js'; import { usePopIn } from './hooks/usePopIn.js'; import { useRowHighlight } from './hooks/useRowHighlight.js'; import { useRowNavigationIndicators } from './hooks/useRowNavigationIndicator.js'; @@ -201,7 +201,7 @@ const AnalyticalTable = forwardRef 0) { return { ...tableStyles, @@ -667,7 +670,7 @@ const AnalyticalTable = forwardRef { if (retainColumnWidth && tableState.columnResizing?.isResizingColumn && tableState.tableColResized == null) { @@ -678,7 +681,7 @@ const AnalyticalTable = forwardRef { columnVirtualizer.measure(); @@ -890,7 +893,7 @@ const AnalyticalTable = forwardRef )}
- {!isFirefox && (additionalEmptyRowsCount || tableState.isScrollable) && ( + {!nativeScrollbar && (additionalEmptyRowsCount || tableState.isScrollable) && (