The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.tsx

176 lines
4.9 KiB

import React, { FC, useCallback, useEffect, useRef } from 'react';
import isNil from 'lodash/isNil';
import classNames from 'classnames';
import { css } from '@emotion/css';
import Scrollbars from 'react-custom-scrollbars';
import { useStyles } from '../../themes';
import { GrafanaTheme } from '@grafana/data';
interface Props {
className?: string;
autoHide?: boolean;
autoHideTimeout?: number;
autoHeightMax?: string;
hideTracksWhenNotNeeded?: boolean;
hideHorizontalTrack?: boolean;
hideVerticalTrack?: boolean;
scrollTop?: number;
setScrollTop?: (event: any) => void;
autoHeightMin?: number | string;
updateAfterMountMs?: number;
}
/**
* Wraps component into <Scrollbars> component from `react-custom-scrollbars`
*/
export const CustomScrollbar: FC<Props> = ({
autoHide = false,
autoHideTimeout = 200,
setScrollTop,
className,
autoHeightMin = '0',
autoHeightMax = '100%',
hideTracksWhenNotNeeded = false,
hideHorizontalTrack,
hideVerticalTrack,
updateAfterMountMs,
scrollTop,
children,
}) => {
const ref = useRef<Scrollbars>(null);
const styles = useStyles(getStyles);
const updateScroll = () => {
if (ref.current && !isNil(scrollTop)) {
ref.current.scrollTop(scrollTop);
}
};
useEffect(() => {
updateScroll();
});
/**
* Special logic for doing a update a few milliseconds after mount to check for
* updated height due to dynamic content
*/
useEffect(() => {
if (!updateAfterMountMs) {
return;
}
setTimeout(() => {
const scrollbar = ref.current as any;
if (scrollbar?.update) {
scrollbar.update();
}
}, updateAfterMountMs);
}, [updateAfterMountMs]);
function renderTrack(className: string, hideTrack: boolean | undefined, passedProps: any) {
if (passedProps.style && hideTrack) {
passedProps.style.display = 'none';
}
return <div {...passedProps} className={className} />;
}
const renderTrackHorizontal = useCallback(
(passedProps: any) => {
return renderTrack('track-horizontal', hideHorizontalTrack, passedProps);
},
[hideHorizontalTrack]
);
const renderTrackVertical = useCallback(
(passedProps: any) => {
return renderTrack('track-vertical', hideVerticalTrack, passedProps);
},
[hideVerticalTrack]
);
const renderThumbHorizontal = useCallback((passedProps: any) => {
return <div {...passedProps} className="thumb-horizontal" />;
}, []);
const renderThumbVertical = useCallback((passedProps: any) => {
return <div {...passedProps} className="thumb-vertical" />;
}, []);
const renderView = useCallback((passedProps: any) => {
return <div {...passedProps} className="scrollbar-view" />;
}, []);
return (
<Scrollbars
ref={ref}
className={classNames(styles.customScrollbar, className)}
onScroll={setScrollTop}
autoHeight={true}
autoHide={autoHide}
autoHideTimeout={autoHideTimeout}
hideTracksWhenNotNeeded={hideTracksWhenNotNeeded}
// These autoHeightMin & autoHeightMax options affect firefox and chrome differently.
// Before these where set to inherit but that caused problems with cut of legends in firefox
autoHeightMax={autoHeightMax}
autoHeightMin={autoHeightMin}
renderTrackHorizontal={renderTrackHorizontal}
renderTrackVertical={renderTrackVertical}
renderThumbHorizontal={renderThumbHorizontal}
renderThumbVertical={renderThumbVertical}
renderView={renderView}
>
{children}
</Scrollbars>
);
};
export default CustomScrollbar;
const getStyles = (theme: GrafanaTheme) => {
return {
customScrollbar: css`
// Fix for Firefox. For some reason sometimes .view container gets a height of its content, but in order to
// make scroll working it should fit outer container size (scroll appears only when inner container size is
// greater than outer one).
display: flex;
flex-grow: 1;
.scrollbar-view {
display: flex;
flex-grow: 1;
flex-direction: column;
}
.track-vertical {
border-radius: ${theme.border.radius.md};
width: ${theme.spacing.sm} !important;
right: 0px;
bottom: ${theme.spacing.xxs};
top: ${theme.spacing.xxs};
}
.track-horizontal {
border-radius: ${theme.border.radius.md};
height: ${theme.spacing.sm} !important;
right: ${theme.spacing.xxs};
bottom: ${theme.spacing.xxs};
left: ${theme.spacing.xxs};
}
.thumb-vertical {
background: ${theme.colors.bg3};
border-radius: ${theme.border.radius.md};
opacity: 0;
}
.thumb-horizontal {
background: ${theme.colors.bg3};
border-radius: ${theme.border.radius.md};
opacity: 0;
}
&:hover {
.thumb-vertical,
.thumb-horizontal {
opacity: 1;
transition: opacity 0.3s ease-in-out;
}
}
`,
};
};