ScrollContainer: Replace CustomScrollbar usage (#96229)
* replace CustomScrollbar * use ScrollContainer in RolePickerMenu and PanelDataPane
This commit is contained in:
@@ -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>
|
||||
)}
|
||||
|
||||
+6
-15
@@ -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..." />
|
||||
|
||||
Reference in New Issue
Block a user