ScrollContainer: Replace CustomScrollbar usage (#96229)

* replace CustomScrollbar

* use ScrollContainer in RolePickerMenu and PanelDataPane
This commit is contained in:
Ashley Harrison
2024-11-12 15:43:04 +00:00
committed by GitHub
parent 108d611205
commit 1e001451bd
10 changed files with 49 additions and 144 deletions
@@ -12,7 +12,6 @@ import { config } from '@grafana/runtime';
import {
AsyncSelect,
Button,
CustomScrollbar,
FormatOptionLabelMeta,
getSelectStyles,
Icon,
@@ -22,6 +21,7 @@ import {
useStyles2,
useTheme2,
} from '@grafana/ui';
import { ScrollContainer } from '@grafana/ui/src/unstable';
import { PrometheusDatasource } from '../../datasource';
import { truncateResult } from '../../language_utils';
@@ -245,15 +245,9 @@ export function MetricSelect({
style={{ maxHeight: Math.round(maxHeight * 0.9) }}
aria-label="Select options menu"
>
<CustomScrollbar
scrollRefCallback={innerRef}
autoHide={false}
autoHeightMax="inherit"
hideHorizontalTrack
showScrollIndicators
>
<ScrollContainer ref={innerRef} showScrollIndicators>
{children}
</CustomScrollbar>
</ScrollContainer>
{optionsLoaded && (
<div className={styles.customMenuFooter}>
<div>
@@ -1,8 +1,9 @@
import { css, cx } from '@emotion/css';
import { useEffect, useRef, useState } from 'react';
import { Button, CustomScrollbar, Stack, TextLink, useStyles2, useTheme2 } from '@grafana/ui';
import { Button, Stack, TextLink, useStyles2, useTheme2 } from '@grafana/ui';
import { getSelectStyles } from '@grafana/ui/src/components/Select/getSelectStyles';
import { ScrollContainer } from '@grafana/ui/src/unstable';
import { OrgRole, Role } from 'app/types';
import { BuiltinRoleSelector } from './BuiltinRoleSelector';
@@ -217,14 +218,11 @@ export const RolePickerMenu = ({
)}
>
<div className={customStyles.menu} aria-label="Role picker menu">
<CustomScrollbar
autoHide={false}
autoHeightMax={`${MENU_MAX_HEIGHT}px`}
hideHorizontalTrack
hideVerticalTrack
<ScrollContainer
maxHeight={`${MENU_MAX_HEIGHT}px`}
// NOTE: this is a way to force hiding of the scrollbar
// the scrollbar makes the mouseEvents drop
className={cx(customStyles.hideScrollBar)}
scrollbarWidth="none"
>
{showBasicRole && (
<div className={customStyles.menuSection}>
@@ -254,7 +252,7 @@ export const RolePickerMenu = ({
showOnLeftSubMenu={menuLeft}
/>
))}
</CustomScrollbar>
</ScrollContainer>
<div className={customStyles.menuButtonRow}>
<Stack justifyContent="flex-end">
<Button size="sm" fill="text" onClick={onClearInternal} disabled={updateDisabled}>
@@ -1,7 +1,8 @@
import { cx } from '@emotion/css';
import { Button, CustomScrollbar, Stack, useStyles2, useTheme2 } from '@grafana/ui';
import { Button, Stack, useStyles2, useTheme2 } from '@grafana/ui';
import { getSelectStyles } from '@grafana/ui/src/components/Select/getSelectStyles';
import { ScrollContainer } from '@grafana/ui/src/unstable';
import { Role } from 'app/types';
import { RoleMenuOption } from './RoleMenuOption';
@@ -41,7 +42,7 @@ export const RolePickerSubMenu = ({
className={cx(customStyles.subMenu, { [customStyles.subMenuLeft]: showOnLeft })}
aria-label="Role picker submenu"
>
<CustomScrollbar autoHide={false} autoHeightMax={`${MENU_MAX_HEIGHT}px`} hideHorizontalTrack>
<ScrollContainer maxHeight={`${MENU_MAX_HEIGHT}px`}>
<div className={styles.optionBody}>
{options.map((option, i) => (
<RoleMenuOption
@@ -62,7 +63,7 @@ export const RolePickerSubMenu = ({
/>
))}
</div>
</CustomScrollbar>
</ScrollContainer>
<div className={customStyles.subMenuButtonRow}>
<Stack justifyContent="flex-end">
<Button size="sm" fill="text" onClick={onClearInternal}>
@@ -11,7 +11,8 @@ import {
SceneObjectUrlValues,
VizPanel,
} from '@grafana/scenes';
import { Container, CustomScrollbar, TabContent, TabsBar, useStyles2 } from '@grafana/ui';
import { Container, TabContent, TabsBar, useStyles2 } from '@grafana/ui';
import { ScrollContainer } from '@grafana/ui/src/unstable';
import { getConfig } from 'app/core/config';
import { contextSrv } from 'app/core/core';
import { getRulesPermissions } from 'app/features/alerting/unified/utils/access-control';
@@ -83,11 +84,11 @@ function PanelDataPaneRendered({ model }: SceneComponentProps<PanelDataPane>) {
<TabsBar hideBorder={true} className={styles.tabsBar}>
{tabs.map((t) => t.renderTab({ active: t.tabId === tab, onChangeTab: () => model.onChangeTab(t) }))}
</TabsBar>
<CustomScrollbar className={styles.scroll}>
<ScrollContainer backgroundColor="primary">
<TabContent className={styles.tabContent}>
<Container>{currentTab && <currentTab.Component model={currentTab} />}</Container>
</TabContent>
</CustomScrollbar>
</ScrollContainer>
</div>
);
}
@@ -128,8 +129,5 @@ function getStyles(theme: GrafanaTheme2) {
flexShrink: 0,
paddingLeft: theme.spacing(2),
}),
scroll: css({
background: theme.colors.background.primary,
}),
};
}
@@ -1,85 +0,0 @@
import { css } from '@emotion/css';
import { useMemo, useState } from 'react';
import { GrafanaTheme2, PanelPluginMeta } from '@grafana/data';
import { locationService } from '@grafana/runtime';
import { CustomScrollbar, Icon, Input, Modal, useStyles2 } from '@grafana/ui';
import { onCreateNewWidgetPanel } from 'app/features/dashboard/utils/dashboard';
import { VizTypePickerPlugin } from 'app/features/panel/components/VizTypePicker/VizTypePickerPlugin';
import { filterPluginList, getWidgetPluginMeta } from 'app/features/panel/state/util';
import { useSelector } from 'app/types';
export const AddWidgetModal = () => {
const styles = useStyles2(getStyles);
const [searchQuery, setSearchQuery] = useState('');
const dashboard = useSelector((state) => state.dashboard.getModel());
const widgetsList: PanelPluginMeta[] = useMemo(() => {
return getWidgetPluginMeta();
}, []);
const filteredWidgetsTypes = useMemo((): PanelPluginMeta[] => {
return filterPluginList(widgetsList, searchQuery);
}, [widgetsList, searchQuery]);
const onDismiss = () => {
locationService.partial({ addWidget: null });
};
return (
<Modal
title="Select widget type"
closeOnEscape
closeOnBackdropClick
isOpen
className={styles.modal}
onClickBackdrop={onDismiss}
onDismiss={onDismiss}
>
<Input
type="search"
autoFocus
className={styles.searchInput}
value={searchQuery}
prefix={<Icon name="search" />}
placeholder="Search widget"
onChange={(e) => {
setSearchQuery(e.currentTarget.value);
}}
/>
<CustomScrollbar>
<div className={styles.grid}>
{filteredWidgetsTypes.map((plugin, index) => (
<VizTypePickerPlugin
disabled={false}
key={plugin.id}
isCurrent={false}
plugin={plugin}
onClick={(e) => {
const id = onCreateNewWidgetPanel(dashboard!, plugin.id);
locationService.partial({ editPanel: id, addWidget: null });
}}
/>
))}
</div>
</CustomScrollbar>
</Modal>
);
};
const getStyles = (theme: GrafanaTheme2) => ({
modal: css({
width: '65%',
maxWidth: '960px',
[theme.breakpoints.down('md')]: {
width: '100%',
},
}),
searchInput: css({
marginBottom: theme.spacing(2),
}),
grid: css({
display: 'grid',
gridGap: theme.spacing(1),
}),
});
@@ -6,8 +6,9 @@ import { GrafanaTheme2, ScopedVars } from '@grafana/data';
import { sanitize, sanitizeUrl } from '@grafana/data/src/text/sanitize';
import { selectors } from '@grafana/e2e-selectors';
import { DashboardLink } from '@grafana/schema';
import { CustomScrollbar, Dropdown, Icon, Button, Menu, useStyles2 } from '@grafana/ui';
import { Dropdown, Icon, Button, Menu, useStyles2 } from '@grafana/ui';
import { ButtonLinkProps, LinkButton } from '@grafana/ui/src/components/Button';
import { ScrollContainer } from '@grafana/ui/src/unstable';
import { getBackendSrv } from 'app/core/services/backend_srv';
import { DashboardSearchItem } from 'app/features/search/types';
@@ -36,7 +37,7 @@ function DashboardLinksMenu({ dashboardUID, link }: DashboardLinksMenuProps) {
return (
<Menu>
<div className={styles.dropdown}>
<CustomScrollbar>
<ScrollContainer maxHeight="inherit">
{resolvedLinks.map((resolvedLink, index) => {
return (
<Menu.Item
@@ -49,7 +50,7 @@ function DashboardLinksMenu({ dashboardUID, link }: DashboardLinksMenuProps) {
/>
);
})}
</CustomScrollbar>
</ScrollContainer>
</div>
</Menu>
);
@@ -157,7 +158,6 @@ function getStyles(theme: GrafanaTheme2) {
dropdown: css({
maxWidth: 'max(30vw, 300px)',
maxHeight: '70vh',
overflowY: 'auto',
}),
button: css({
color: theme.colors.text.primary,
@@ -16,7 +16,8 @@ import {
import { selectors } from '@grafana/e2e-selectors';
import { getDataSourceSrv, locationService } from '@grafana/runtime';
import { DataQuery } from '@grafana/schema';
import { Button, CustomScrollbar, HorizontalGroup, InlineFormLabel, Modal, stylesFactory } from '@grafana/ui';
import { Button, HorizontalGroup, InlineFormLabel, Modal, stylesFactory } from '@grafana/ui';
import { ScrollContainer } from '@grafana/ui/src/unstable';
import { PluginHelp } from 'app/core/components/PluginHelp/PluginHelp';
import config from 'app/core/config';
import { backendSrv } from 'app/core/services/backend_srv';
@@ -331,7 +332,7 @@ export class QueryGroup extends PureComponent<Props, State> {
const styles = getStyles();
return (
<CustomScrollbar autoHeightMin="100%" scrollRefCallback={this.setScrollRef}>
<ScrollContainer minHeight="100%" ref={this.setScrollRef}>
<div className={styles.innerWrapper}>
{this.renderTopSection(styles)}
{dsSettings && (
@@ -346,7 +347,7 @@ export class QueryGroup extends PureComponent<Props, State> {
</>
)}
</div>
</CustomScrollbar>
</ScrollContainer>
);
}
}
@@ -2,7 +2,8 @@ import { css, cx } from '@emotion/css';
import { Dictionary } from 'lodash';
import { GrafanaTheme2 } from '@grafana/data';
import { Checkbox, CustomScrollbar, Icon, RadioButtonDot, useStyles2 } from '@grafana/ui';
import { Checkbox, Icon, RadioButtonDot, useStyles2 } from '@grafana/ui';
import { ScrollContainer } from '@grafana/ui/src/unstable';
import { t } from 'app/core/internationalization';
import { ScopesTree } from './ScopesTree';
@@ -129,12 +130,12 @@ export function ScopesTreeItem({
if (isLastExpandedNode) {
return (
<CustomScrollbar
autoHeightMin={`${Math.min(5, nodes.length) * 30}px`}
autoHeightMax={nodeReason === NodeReason.Persisted ? `${Math.min(5, nodes.length) * 30}px` : '100%'}
<ScrollContainer
minHeight={`${Math.min(5, nodes.length) * 30}px`}
maxHeight={nodeReason === NodeReason.Persisted ? `${Math.min(5, nodes.length) * 30}px` : '100%'}
>
{children}
</CustomScrollbar>
</ScrollContainer>
);
}
@@ -4,9 +4,10 @@ import { FormEvent, useCallback, useEffect, useRef, useState } from 'react';
import * as React from 'react';
import { GrafanaTheme2, VariableSuggestion } from '@grafana/data';
import { CustomScrollbar, FieldValidationMessage, Portal, TextArea, useTheme2 } from '@grafana/ui';
import { FieldValidationMessage, Portal, TextArea, useTheme2 } from '@grafana/ui';
import { DataLinkSuggestions } from '@grafana/ui/src/components/DataLinks/DataLinkSuggestions';
import { Input } from '@grafana/ui/src/components/Input/Input';
import { ScrollContainer } from '@grafana/ui/src/unstable';
const modulo = (a: number, n: number) => a - n * Math.floor(a / n);
const ERROR_TOOLTIP_OFFSET = 8;
@@ -61,6 +62,7 @@ export const SuggestionsInput = ({
const [showingSuggestions, setShowingSuggestions] = useState(false);
const [suggestionsIndex, setSuggestionsIndex] = useState(0);
const [variableValue, setVariableValue] = useState<string>(value.toString());
const scrollRef = useRef<HTMLDivElement>(null);
const [scrollTop, setScrollTop] = useState(0);
const [inputHeight, setInputHeight] = useState<number>(0);
const [startPos, setStartPos] = useState<number>(0);
@@ -70,6 +72,10 @@ export const SuggestionsInput = ({
const inputRef = useRef<HTMLInputElement | HTMLTextAreaElement>();
useEffect(() => {
scrollRef.current?.scrollTo(0, scrollTop);
}, [scrollTop]);
// the order of middleware is important!
const middleware = [
flip({
@@ -189,10 +195,10 @@ export const SuggestionsInput = ({
{showingSuggestions && (
<Portal>
<div ref={refs.setFloating} style={floatingStyles} className={styles.suggestionsWrapper}>
<CustomScrollbar
scrollTop={scrollTop}
autoHeightMax="300px"
setScrollTop={({ scrollTop }) => setScrollTop(scrollTop)}
<ScrollContainer
maxHeight="300px"
onScroll={(event) => setScrollTop(event.currentTarget.scrollTop ?? 0)}
ref={scrollRef}
>
{/* This suggestion component has a specialized name,
but is rather generalistic in implementation,
@@ -205,7 +211,7 @@ export const SuggestionsInput = ({
onClose={() => setShowingSuggestions(false)}
activeIndex={suggestionsIndex}
/>
</CustomScrollbar>
</ScrollContainer>
</div>
</Portal>
)}
@@ -3,17 +3,8 @@ import { useEffect, useMemo, useState } from 'react';
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
import { reportInteraction } from '@grafana/runtime';
import {
Button,
Card,
Collapse,
CustomScrollbar,
Field,
Input,
LoadingPlaceholder,
Select,
useStyles2,
} from '@grafana/ui';
import { Button, Card, Collapse, Field, Input, LoadingPlaceholder, Select, useStyles2 } from '@grafana/ui';
import { ScrollContainer } from '@grafana/ui/src/unstable';
import AzureLogAnalyticsDatasource from '../../azure_log_analytics/azure_log_analytics_datasource';
import {
@@ -171,7 +162,7 @@ const AzureCheatSheet = (props: AzureCheatSheetProps) => {
return totalQueries;
}, 0)}
</div>
<CustomScrollbar showScrollIndicators={true} autoHeightMax="350px">
<ScrollContainer showScrollIndicators maxHeight="350px">
{Object.keys(visibleQueries).map((category: string) => {
if (visibleQueries[category]!.length) {
return (
@@ -186,14 +177,14 @@ const AzureCheatSheet = (props: AzureCheatSheetProps) => {
return (
<Card className={styles.card} key={query.id}>
<Card.Heading>{query.displayName}</Card.Heading>
<CustomScrollbar showScrollIndicators={true} autoHeightMax="100px">
<ScrollContainer showScrollIndicators maxHeight="100px">
<RawQuery
aria-label={`${query.displayName} raw query`}
query={query.body}
lang={lang}
className={styles.rawQuery}
/>
</CustomScrollbar>
</ScrollContainer>
<Card.Actions>
<Button
size="sm"
@@ -224,7 +215,7 @@ const AzureCheatSheet = (props: AzureCheatSheetProps) => {
}
return;
})}
</CustomScrollbar>
</ScrollContainer>
</div>
) : (
<LoadingPlaceholder text="Loading..." />