mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Chore: use ScrollContainer
across frontend platform components (#95601)
* use ScrollContainer in grafana-ui * remove extraneous labels * fix unit tests
This commit is contained in:
parent
9f43724b57
commit
ada6249280
@ -13,9 +13,9 @@ import { DataLinkBuiltInVars, GrafanaTheme2, VariableOrigin, VariableSuggestion
|
|||||||
import { SlatePrism } from '../../slate-plugins';
|
import { SlatePrism } from '../../slate-plugins';
|
||||||
import { useStyles2 } from '../../themes';
|
import { useStyles2 } from '../../themes';
|
||||||
import { SCHEMA, makeValue } from '../../utils/slate';
|
import { SCHEMA, makeValue } from '../../utils/slate';
|
||||||
import CustomScrollbar from '../CustomScrollbar/CustomScrollbar';
|
|
||||||
import { getInputStyles } from '../Input/Input';
|
import { getInputStyles } from '../Input/Input';
|
||||||
import { Portal } from '../Portal/Portal';
|
import { Portal } from '../Portal/Portal';
|
||||||
|
import { ScrollContainer } from '../ScrollContainer/ScrollContainer';
|
||||||
|
|
||||||
import { DataLinkSuggestions } from './DataLinkSuggestions';
|
import { DataLinkSuggestions } from './DataLinkSuggestions';
|
||||||
import { SelectionReference } from './SelectionReference';
|
import { SelectionReference } from './SelectionReference';
|
||||||
@ -86,6 +86,11 @@ export const DataLinkInput = memo(
|
|||||||
const [linkUrl, setLinkUrl] = useState<Value>(makeValue(value));
|
const [linkUrl, setLinkUrl] = useState<Value>(makeValue(value));
|
||||||
const prevLinkUrl = usePrevious<Value>(linkUrl);
|
const prevLinkUrl = usePrevious<Value>(linkUrl);
|
||||||
const [scrollTop, setScrollTop] = useState(0);
|
const [scrollTop, setScrollTop] = useState(0);
|
||||||
|
const scrollRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
scrollRef.current?.scrollTo(0, scrollTop);
|
||||||
|
}, [scrollTop]);
|
||||||
|
|
||||||
// the order of middleware is important!
|
// the order of middleware is important!
|
||||||
const middleware = [
|
const middleware = [
|
||||||
@ -208,10 +213,10 @@ export const DataLinkInput = memo(
|
|||||||
{showingSuggestions && (
|
{showingSuggestions && (
|
||||||
<Portal>
|
<Portal>
|
||||||
<div ref={refs.setFloating} style={floatingStyles}>
|
<div ref={refs.setFloating} style={floatingStyles}>
|
||||||
<CustomScrollbar
|
<ScrollContainer
|
||||||
scrollTop={scrollTop}
|
maxHeight="300px"
|
||||||
autoHeightMax="300px"
|
ref={scrollRef}
|
||||||
setScrollTop={({ scrollTop }) => setScrollTop(scrollTop)}
|
onScroll={(event) => setScrollTop(event.currentTarget.scrollTop)}
|
||||||
>
|
>
|
||||||
<DataLinkSuggestions
|
<DataLinkSuggestions
|
||||||
activeRef={activeRef}
|
activeRef={activeRef}
|
||||||
@ -220,7 +225,7 @@ export const DataLinkInput = memo(
|
|||||||
onClose={() => setShowingSuggestions(false)}
|
onClose={() => setShowingSuggestions(false)}
|
||||||
activeIndex={suggestionsIndex}
|
activeIndex={suggestionsIndex}
|
||||||
/>
|
/>
|
||||||
</CustomScrollbar>
|
</ScrollContainer>
|
||||||
</div>
|
</div>
|
||||||
</Portal>
|
</Portal>
|
||||||
)}
|
)}
|
||||||
|
@ -10,10 +10,10 @@ import { RelativeTimeRange, GrafanaTheme2, TimeOption } from '@grafana/data';
|
|||||||
import { useStyles2 } from '../../../themes';
|
import { useStyles2 } from '../../../themes';
|
||||||
import { Trans, t } from '../../../utils/i18n';
|
import { Trans, t } from '../../../utils/i18n';
|
||||||
import { Button } from '../../Button';
|
import { Button } from '../../Button';
|
||||||
import CustomScrollbar from '../../CustomScrollbar/CustomScrollbar';
|
|
||||||
import { Field } from '../../Forms/Field';
|
import { Field } from '../../Forms/Field';
|
||||||
import { Icon } from '../../Icon/Icon';
|
import { Icon } from '../../Icon/Icon';
|
||||||
import { getInputStyles, Input } from '../../Input/Input';
|
import { getInputStyles, Input } from '../../Input/Input';
|
||||||
|
import { ScrollContainer } from '../../ScrollContainer/ScrollContainer';
|
||||||
import { Tooltip } from '../../Tooltip/Tooltip';
|
import { Tooltip } from '../../Tooltip/Tooltip';
|
||||||
import { TimePickerTitle } from '../TimeRangePicker/TimePickerTitle';
|
import { TimePickerTitle } from '../TimeRangePicker/TimePickerTitle';
|
||||||
import { TimeRangeList } from '../TimeRangePicker/TimeRangeList';
|
import { TimeRangeList } from '../TimeRangePicker/TimeRangeList';
|
||||||
@ -157,14 +157,16 @@ export function RelativeTimeRangePicker(props: RelativeTimeRangePickerProps) {
|
|||||||
<div ref={ref} {...overlayProps} {...dialogProps}>
|
<div ref={ref} {...overlayProps} {...dialogProps}>
|
||||||
<div className={styles.content} ref={refs.setFloating} style={floatingStyles} {...getFloatingProps()}>
|
<div className={styles.content} ref={refs.setFloating} style={floatingStyles} {...getFloatingProps()}>
|
||||||
<div className={styles.body}>
|
<div className={styles.body}>
|
||||||
<CustomScrollbar className={styles.leftSide} hideHorizontalTrack>
|
<div className={styles.leftSide}>
|
||||||
<TimeRangeList
|
<ScrollContainer showScrollIndicators>
|
||||||
title={t('time-picker.time-range.example-title', 'Example time ranges')}
|
<TimeRangeList
|
||||||
options={validOptions}
|
title={t('time-picker.time-range.example-title', 'Example time ranges')}
|
||||||
onChange={onChangeTimeOption}
|
options={validOptions}
|
||||||
value={timeOption}
|
onChange={onChangeTimeOption}
|
||||||
/>
|
value={timeOption}
|
||||||
</CustomScrollbar>
|
/>
|
||||||
|
</ScrollContainer>
|
||||||
|
</div>
|
||||||
<div className={styles.rightSide}>
|
<div className={styles.rightSide}>
|
||||||
<div className={styles.title}>
|
<div className={styles.title}>
|
||||||
<TimePickerTitle>
|
<TimePickerTitle>
|
||||||
|
@ -11,9 +11,9 @@ import { selectors } from '@grafana/e2e-selectors';
|
|||||||
|
|
||||||
import { useStyles2 } from '../../themes';
|
import { useStyles2 } from '../../themes';
|
||||||
import { t } from '../../utils/i18n';
|
import { t } from '../../utils/i18n';
|
||||||
import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar';
|
|
||||||
import { getDragStyles } from '../DragHandle/DragHandle';
|
import { getDragStyles } from '../DragHandle/DragHandle';
|
||||||
import { IconButton } from '../IconButton/IconButton';
|
import { IconButton } from '../IconButton/IconButton';
|
||||||
|
import { ScrollContainer } from '../ScrollContainer/ScrollContainer';
|
||||||
import { Text } from '../Text/Text';
|
import { Text } from '../Text/Text';
|
||||||
|
|
||||||
import 'rc-drawer/assets/index.css';
|
import 'rc-drawer/assets/index.css';
|
||||||
@ -167,7 +167,7 @@ export function Drawer({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{typeof title !== 'string' && title}
|
{typeof title !== 'string' && title}
|
||||||
{!scrollableContent ? content : <CustomScrollbar>{content}</CustomScrollbar>}
|
{!scrollableContent ? content : <ScrollContainer showScrollIndicators>{content}</ScrollContainer>}
|
||||||
</div>
|
</div>
|
||||||
</FocusScope>
|
</FocusScope>
|
||||||
</RcDrawer>
|
</RcDrawer>
|
||||||
|
@ -9,7 +9,7 @@ import Creatable from 'react-select/creatable';
|
|||||||
// Components
|
// Components
|
||||||
import { SelectableValue, ThemeContext } from '@grafana/data';
|
import { SelectableValue, ThemeContext } from '@grafana/data';
|
||||||
|
|
||||||
import { CustomScrollbar } from '../../../CustomScrollbar/CustomScrollbar';
|
import { ScrollContainer } from '../../../ScrollContainer/ScrollContainer';
|
||||||
import { SingleValue } from '../../../Select/SingleValue';
|
import { SingleValue } from '../../../Select/SingleValue';
|
||||||
import resetSelectStyles from '../../../Select/resetSelectStyles';
|
import resetSelectStyles from '../../../Select/resetSelectStyles';
|
||||||
import { SelectCommonProps, SelectAsyncProps } from '../../../Select/types';
|
import { SelectCommonProps, SelectAsyncProps } from '../../../Select/types';
|
||||||
@ -45,9 +45,9 @@ export interface LegacySelectProps<T> extends LegacyCommonProps<T> {
|
|||||||
export const MenuList = (props: MenuListProps) => {
|
export const MenuList = (props: MenuListProps) => {
|
||||||
return (
|
return (
|
||||||
<components.MenuList {...props}>
|
<components.MenuList {...props}>
|
||||||
<CustomScrollbar autoHide={false} autoHeightMax="inherit">
|
<ScrollContainer showScrollIndicators overflowX="hidden" maxHeight="inherit">
|
||||||
{props.children}
|
{props.children}
|
||||||
</CustomScrollbar>
|
</ScrollContainer>
|
||||||
</components.MenuList>
|
</components.MenuList>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -267,9 +267,6 @@ describe('SelectBase', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('toggle all', () => {
|
describe('toggle all', () => {
|
||||||
beforeEach(() => {
|
|
||||||
jest.resetAllMocks();
|
|
||||||
});
|
|
||||||
it('renders menu with select all toggle', async () => {
|
it('renders menu with select all toggle', async () => {
|
||||||
render(
|
render(
|
||||||
<SelectBase
|
<SelectBase
|
||||||
|
@ -10,8 +10,8 @@ import { selectors } from '@grafana/e2e-selectors';
|
|||||||
import { useTheme2 } from '../../themes/ThemeContext';
|
import { useTheme2 } from '../../themes/ThemeContext';
|
||||||
import { Trans } from '../../utils/i18n';
|
import { Trans } from '../../utils/i18n';
|
||||||
import { clearButtonStyles } from '../Button';
|
import { clearButtonStyles } from '../Button';
|
||||||
import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar';
|
|
||||||
import { Icon } from '../Icon/Icon';
|
import { Icon } from '../Icon/Icon';
|
||||||
|
import { ScrollContainer } from '../ScrollContainer/ScrollContainer';
|
||||||
|
|
||||||
import { getSelectStyles } from './getSelectStyles';
|
import { getSelectStyles } from './getSelectStyles';
|
||||||
import { ToggleAllState } from './types';
|
import { ToggleAllState } from './types';
|
||||||
@ -54,7 +54,7 @@ export const SelectMenu = ({
|
|||||||
style={{ maxHeight }}
|
style={{ maxHeight }}
|
||||||
aria-label="Select options menu"
|
aria-label="Select options menu"
|
||||||
>
|
>
|
||||||
<CustomScrollbar scrollRefCallback={innerRef} autoHide={false} autoHeightMax="inherit" hideHorizontalTrack>
|
<ScrollContainer ref={innerRef} maxHeight="inherit" overflowX="hidden" showScrollIndicators>
|
||||||
{toggleAllOptions && (
|
{toggleAllOptions && (
|
||||||
<ToggleAllOption
|
<ToggleAllOption
|
||||||
state={toggleAllOptions.state}
|
state={toggleAllOptions.state}
|
||||||
@ -64,7 +64,7 @@ export const SelectMenu = ({
|
|||||||
></ToggleAllOption>
|
></ToggleAllOption>
|
||||||
)}
|
)}
|
||||||
{children}
|
{children}
|
||||||
</CustomScrollbar>
|
</ScrollContainer>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -8,7 +8,7 @@ import { IconButton } from '../../components/IconButton/IconButton';
|
|||||||
import { TabsBar, Tab, TabContent } from '../../components/Tabs';
|
import { TabsBar, Tab, TabContent } from '../../components/Tabs';
|
||||||
import { useStyles2, useTheme2 } from '../../themes';
|
import { useStyles2, useTheme2 } from '../../themes';
|
||||||
import { IconName } from '../../types/icon';
|
import { IconName } from '../../types/icon';
|
||||||
import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar';
|
import { ScrollContainer } from '../ScrollContainer/ScrollContainer';
|
||||||
|
|
||||||
export interface TabConfig {
|
export interface TabConfig {
|
||||||
label: string;
|
label: string;
|
||||||
@ -50,9 +50,9 @@ export function TabbedContainer({ tabs, defaultTab, closeIconTooltip, onClose, t
|
|||||||
))}
|
))}
|
||||||
<IconButton className={styles.close} onClick={onClose} name="times" tooltip={closeIconTooltip ?? 'Close'} />
|
<IconButton className={styles.close} onClick={onClose} name="times" tooltip={closeIconTooltip ?? 'Close'} />
|
||||||
</TabsBar>
|
</TabsBar>
|
||||||
<CustomScrollbar autoHeightMin={autoHeight} autoHeightMax={autoHeight}>
|
<ScrollContainer height={autoHeight}>
|
||||||
<TabContent className={styles.tabContent}>{tabs.find((t) => t.value === activeTab)?.content}</TabContent>
|
<TabContent className={styles.tabContent}>{tabs.find((t) => t.value === activeTab)?.content}</TabContent>
|
||||||
</CustomScrollbar>
|
</ScrollContainer>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Meta, StoryFn } from '@storybook/react';
|
import { Meta, StoryFn } from '@storybook/react';
|
||||||
|
|
||||||
import { Button } from '../Button';
|
import { Button } from '../Button';
|
||||||
import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar';
|
import { ScrollContainer } from '../ScrollContainer/ScrollContainer';
|
||||||
import mdx from '../Toggletip/Toggletip.mdx';
|
import mdx from '../Toggletip/Toggletip.mdx';
|
||||||
|
|
||||||
import { Toggletip } from './Toggletip';
|
import { Toggletip } from './Toggletip';
|
||||||
@ -96,7 +96,7 @@ export const LongContent: StoryFn<typeof Toggletip> = ({
|
|||||||
<Toggletip
|
<Toggletip
|
||||||
title={<h2>Toggletip with scrollable content and no interactive controls</h2>}
|
title={<h2>Toggletip with scrollable content and no interactive controls</h2>}
|
||||||
content={
|
content={
|
||||||
<CustomScrollbar autoHeightMax="500px">
|
<ScrollContainer maxHeight="500px">
|
||||||
{/* one of the few documented cases we can turn this rule off */}
|
{/* one of the few documented cases we can turn this rule off */}
|
||||||
{/* https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/no-noninteractive-tabindex.md#case-shouldnt-i-add-a-tabindex-so-that-users-can-navigate-to-this-item */}
|
{/* https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/no-noninteractive-tabindex.md#case-shouldnt-i-add-a-tabindex-so-that-users-can-navigate-to-this-item */}
|
||||||
{/* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */}
|
{/* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */}
|
||||||
@ -110,7 +110,7 @@ export const LongContent: StoryFn<typeof Toggletip> = ({
|
|||||||
<p key={i}>This is some content repeated over and over again to ensure it is scrollable.</p>
|
<p key={i}>This is some content repeated over and over again to ensure it is scrollable.</p>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</CustomScrollbar>
|
</ScrollContainer>
|
||||||
}
|
}
|
||||||
footer={footer}
|
footer={footer}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
|
@ -6,7 +6,8 @@ import { useLocation } from 'react-router-dom-v5-compat';
|
|||||||
import { GrafanaTheme2, NavModelItem } from '@grafana/data';
|
import { GrafanaTheme2, NavModelItem } from '@grafana/data';
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
import { config, reportInteraction } from '@grafana/runtime';
|
import { config, reportInteraction } from '@grafana/runtime';
|
||||||
import { CustomScrollbar, Icon, IconButton, useStyles2, Stack } from '@grafana/ui';
|
import { Icon, IconButton, useStyles2, Stack } from '@grafana/ui';
|
||||||
|
import { ScrollContainer } from '@grafana/ui/src/unstable';
|
||||||
import { useGrafana } from 'app/core/context/GrafanaContext';
|
import { useGrafana } from 'app/core/context/GrafanaContext';
|
||||||
import { t } from 'app/core/internationalization';
|
import { t } from 'app/core/internationalization';
|
||||||
import { setBookmark } from 'app/core/reducers/navBarTree';
|
import { setBookmark } from 'app/core/reducers/navBarTree';
|
||||||
@ -134,7 +135,7 @@ export const MegaMenu = memo(
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<nav className={styles.content}>
|
<nav className={styles.content}>
|
||||||
<CustomScrollbar showScrollIndicators hideHorizontalTrack>
|
<ScrollContainer height="100%" overflowX="hidden" showScrollIndicators>
|
||||||
<ul className={styles.itemList} aria-label={t('navigation.megamenu.list-label', 'Navigation')}>
|
<ul className={styles.itemList} aria-label={t('navigation.megamenu.list-label', 'Navigation')}>
|
||||||
{navItems.map((link, index) => (
|
{navItems.map((link, index) => (
|
||||||
<Stack key={link.text} direction={index === 0 ? 'row-reverse' : 'row'} alignItems="start">
|
<Stack key={link.text} direction={index === 0 ? 'row-reverse' : 'row'} alignItems="start">
|
||||||
@ -162,7 +163,7 @@ export const MegaMenu = memo(
|
|||||||
</Stack>
|
</Stack>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</CustomScrollbar>
|
</ScrollContainer>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -120,8 +120,6 @@ beforeEach(() => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => jest.resetAllMocks());
|
|
||||||
|
|
||||||
describe('Silences', () => {
|
describe('Silences', () => {
|
||||||
it(
|
it(
|
||||||
'loads and shows silences',
|
'loads and shows silences',
|
||||||
|
@ -185,19 +185,19 @@ describe('SpanFilters', () => {
|
|||||||
jest.advanceTimersByTime(1000);
|
jest.advanceTimersByTime(1000);
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
const container = screen.getByText('TagKey0').parentElement?.parentElement?.parentElement;
|
const container = screen.getByText('TagKey0').parentElement?.parentElement?.parentElement;
|
||||||
expect(container?.childNodes[0].textContent).toBe('ProcessKey0');
|
expect(container?.childNodes[1].textContent).toBe('ProcessKey0');
|
||||||
expect(container?.childNodes[1].textContent).toBe('ProcessKey1');
|
expect(container?.childNodes[2].textContent).toBe('ProcessKey1');
|
||||||
expect(container?.childNodes[2].textContent).toBe('TagKey0');
|
expect(container?.childNodes[3].textContent).toBe('TagKey0');
|
||||||
expect(container?.childNodes[3].textContent).toBe('TagKey1');
|
expect(container?.childNodes[4].textContent).toBe('TagKey1');
|
||||||
expect(container?.childNodes[4].textContent).toBe('id');
|
expect(container?.childNodes[5].textContent).toBe('id');
|
||||||
expect(container?.childNodes[5].textContent).toBe('kind');
|
expect(container?.childNodes[6].textContent).toBe('kind');
|
||||||
expect(container?.childNodes[6].textContent).toBe('library.name');
|
expect(container?.childNodes[7].textContent).toBe('library.name');
|
||||||
expect(container?.childNodes[7].textContent).toBe('library.version');
|
expect(container?.childNodes[8].textContent).toBe('library.version');
|
||||||
expect(container?.childNodes[8].textContent).toBe('status');
|
expect(container?.childNodes[9].textContent).toBe('status');
|
||||||
expect(container?.childNodes[9].textContent).toBe('status.message');
|
expect(container?.childNodes[10].textContent).toBe('status.message');
|
||||||
expect(container?.childNodes[10].textContent).toBe('trace.state');
|
expect(container?.childNodes[11].textContent).toBe('trace.state');
|
||||||
expect(container?.childNodes[11].textContent).toBe('LogKey0');
|
expect(container?.childNodes[12].textContent).toBe('LogKey0');
|
||||||
expect(container?.childNodes[12].textContent).toBe('LogKey1');
|
expect(container?.childNodes[13].textContent).toBe('LogKey1');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user