Chore: use `ScrollContainer` across frontend platform components (#95601)

* use ScrollContainer in grafana-ui

* remove extraneous labels

* fix unit tests
pull/95665/head
Ashley Harrison 8 months ago committed by GitHub
parent 9f43724b57
commit ada6249280
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 17
      packages/grafana-ui/src/components/DataLinks/DataLinkInput.tsx
  2. 20
      packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.tsx
  3. 4
      packages/grafana-ui/src/components/Drawer/Drawer.tsx
  4. 6
      packages/grafana-ui/src/components/Forms/Legacy/Select/Select.tsx
  5. 3
      packages/grafana-ui/src/components/Select/SelectBase.test.tsx
  6. 6
      packages/grafana-ui/src/components/Select/SelectMenu.tsx
  7. 6
      packages/grafana-ui/src/components/TabbedContainer/TabbedContainer.tsx
  8. 6
      packages/grafana-ui/src/components/Toggletip/Toggletip.story.tsx
  9. 7
      public/app/core/components/AppChrome/MegaMenu/MegaMenu.tsx
  10. 2
      public/app/features/alerting/unified/Silences.test.tsx
  11. 26
      public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.test.tsx

@ -13,9 +13,9 @@ import { DataLinkBuiltInVars, GrafanaTheme2, VariableOrigin, VariableSuggestion
import { SlatePrism } from '../../slate-plugins';
import { useStyles2 } from '../../themes';
import { SCHEMA, makeValue } from '../../utils/slate';
import CustomScrollbar from '../CustomScrollbar/CustomScrollbar';
import { getInputStyles } from '../Input/Input';
import { Portal } from '../Portal/Portal';
import { ScrollContainer } from '../ScrollContainer/ScrollContainer';
import { DataLinkSuggestions } from './DataLinkSuggestions';
import { SelectionReference } from './SelectionReference';
@ -86,6 +86,11 @@ export const DataLinkInput = memo(
const [linkUrl, setLinkUrl] = useState<Value>(makeValue(value));
const prevLinkUrl = usePrevious<Value>(linkUrl);
const [scrollTop, setScrollTop] = useState(0);
const scrollRef = useRef<HTMLDivElement>(null);
useEffect(() => {
scrollRef.current?.scrollTo(0, scrollTop);
}, [scrollTop]);
// the order of middleware is important!
const middleware = [
@ -208,10 +213,10 @@ export const DataLinkInput = memo(
{showingSuggestions && (
<Portal>
<div ref={refs.setFloating} style={floatingStyles}>
<CustomScrollbar
scrollTop={scrollTop}
autoHeightMax="300px"
setScrollTop={({ scrollTop }) => setScrollTop(scrollTop)}
<ScrollContainer
maxHeight="300px"
ref={scrollRef}
onScroll={(event) => setScrollTop(event.currentTarget.scrollTop)}
>
<DataLinkSuggestions
activeRef={activeRef}
@ -220,7 +225,7 @@ export const DataLinkInput = memo(
onClose={() => setShowingSuggestions(false)}
activeIndex={suggestionsIndex}
/>
</CustomScrollbar>
</ScrollContainer>
</div>
</Portal>
)}

@ -10,10 +10,10 @@ import { RelativeTimeRange, GrafanaTheme2, TimeOption } from '@grafana/data';
import { useStyles2 } from '../../../themes';
import { Trans, t } from '../../../utils/i18n';
import { Button } from '../../Button';
import CustomScrollbar from '../../CustomScrollbar/CustomScrollbar';
import { Field } from '../../Forms/Field';
import { Icon } from '../../Icon/Icon';
import { getInputStyles, Input } from '../../Input/Input';
import { ScrollContainer } from '../../ScrollContainer/ScrollContainer';
import { Tooltip } from '../../Tooltip/Tooltip';
import { TimePickerTitle } from '../TimeRangePicker/TimePickerTitle';
import { TimeRangeList } from '../TimeRangePicker/TimeRangeList';
@ -157,14 +157,16 @@ export function RelativeTimeRangePicker(props: RelativeTimeRangePickerProps) {
<div ref={ref} {...overlayProps} {...dialogProps}>
<div className={styles.content} ref={refs.setFloating} style={floatingStyles} {...getFloatingProps()}>
<div className={styles.body}>
<CustomScrollbar className={styles.leftSide} hideHorizontalTrack>
<TimeRangeList
title={t('time-picker.time-range.example-title', 'Example time ranges')}
options={validOptions}
onChange={onChangeTimeOption}
value={timeOption}
/>
</CustomScrollbar>
<div className={styles.leftSide}>
<ScrollContainer showScrollIndicators>
<TimeRangeList
title={t('time-picker.time-range.example-title', 'Example time ranges')}
options={validOptions}
onChange={onChangeTimeOption}
value={timeOption}
/>
</ScrollContainer>
</div>
<div className={styles.rightSide}>
<div className={styles.title}>
<TimePickerTitle>

@ -11,9 +11,9 @@ import { selectors } from '@grafana/e2e-selectors';
import { useStyles2 } from '../../themes';
import { t } from '../../utils/i18n';
import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar';
import { getDragStyles } from '../DragHandle/DragHandle';
import { IconButton } from '../IconButton/IconButton';
import { ScrollContainer } from '../ScrollContainer/ScrollContainer';
import { Text } from '../Text/Text';
import 'rc-drawer/assets/index.css';
@ -167,7 +167,7 @@ export function Drawer({
</div>
)}
{typeof title !== 'string' && title}
{!scrollableContent ? content : <CustomScrollbar>{content}</CustomScrollbar>}
{!scrollableContent ? content : <ScrollContainer showScrollIndicators>{content}</ScrollContainer>}
</div>
</FocusScope>
</RcDrawer>

@ -9,7 +9,7 @@ import Creatable from 'react-select/creatable';
// Components
import { SelectableValue, ThemeContext } from '@grafana/data';
import { CustomScrollbar } from '../../../CustomScrollbar/CustomScrollbar';
import { ScrollContainer } from '../../../ScrollContainer/ScrollContainer';
import { SingleValue } from '../../../Select/SingleValue';
import resetSelectStyles from '../../../Select/resetSelectStyles';
import { SelectCommonProps, SelectAsyncProps } from '../../../Select/types';
@ -45,9 +45,9 @@ export interface LegacySelectProps<T> extends LegacyCommonProps<T> {
export const MenuList = (props: MenuListProps) => {
return (
<components.MenuList {...props}>
<CustomScrollbar autoHide={false} autoHeightMax="inherit">
<ScrollContainer showScrollIndicators overflowX="hidden" maxHeight="inherit">
{props.children}
</CustomScrollbar>
</ScrollContainer>
</components.MenuList>
);
};

@ -267,9 +267,6 @@ describe('SelectBase', () => {
});
describe('toggle all', () => {
beforeEach(() => {
jest.resetAllMocks();
});
it('renders menu with select all toggle', async () => {
render(
<SelectBase

@ -10,8 +10,8 @@ import { selectors } from '@grafana/e2e-selectors';
import { useTheme2 } from '../../themes/ThemeContext';
import { Trans } from '../../utils/i18n';
import { clearButtonStyles } from '../Button';
import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar';
import { Icon } from '../Icon/Icon';
import { ScrollContainer } from '../ScrollContainer/ScrollContainer';
import { getSelectStyles } from './getSelectStyles';
import { ToggleAllState } from './types';
@ -54,7 +54,7 @@ export const SelectMenu = ({
style={{ maxHeight }}
aria-label="Select options menu"
>
<CustomScrollbar scrollRefCallback={innerRef} autoHide={false} autoHeightMax="inherit" hideHorizontalTrack>
<ScrollContainer ref={innerRef} maxHeight="inherit" overflowX="hidden" showScrollIndicators>
{toggleAllOptions && (
<ToggleAllOption
state={toggleAllOptions.state}
@ -64,7 +64,7 @@ export const SelectMenu = ({
></ToggleAllOption>
)}
{children}
</CustomScrollbar>
</ScrollContainer>
</div>
);
};

@ -8,7 +8,7 @@ import { IconButton } from '../../components/IconButton/IconButton';
import { TabsBar, Tab, TabContent } from '../../components/Tabs';
import { useStyles2, useTheme2 } from '../../themes';
import { IconName } from '../../types/icon';
import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar';
import { ScrollContainer } from '../ScrollContainer/ScrollContainer';
export interface TabConfig {
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'} />
</TabsBar>
<CustomScrollbar autoHeightMin={autoHeight} autoHeightMax={autoHeight}>
<ScrollContainer height={autoHeight}>
<TabContent className={styles.tabContent}>{tabs.find((t) => t.value === activeTab)?.content}</TabContent>
</CustomScrollbar>
</ScrollContainer>
</div>
);
}

@ -1,7 +1,7 @@
import { Meta, StoryFn } from '@storybook/react';
import { Button } from '../Button';
import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar';
import { ScrollContainer } from '../ScrollContainer/ScrollContainer';
import mdx from '../Toggletip/Toggletip.mdx';
import { Toggletip } from './Toggletip';
@ -96,7 +96,7 @@ export const LongContent: StoryFn<typeof Toggletip> = ({
<Toggletip
title={<h2>Toggletip with scrollable content and no interactive controls</h2>}
content={
<CustomScrollbar autoHeightMax="500px">
<ScrollContainer maxHeight="500px">
{/* 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 */}
{/* 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>
))}
</div>
</CustomScrollbar>
</ScrollContainer>
}
footer={footer}
theme={theme}

@ -6,7 +6,8 @@ import { useLocation } from 'react-router-dom-v5-compat';
import { GrafanaTheme2, NavModelItem } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
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 { t } from 'app/core/internationalization';
import { setBookmark } from 'app/core/reducers/navBarTree';
@ -134,7 +135,7 @@ export const MegaMenu = memo(
</div>
)}
<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')}>
{navItems.map((link, index) => (
<Stack key={link.text} direction={index === 0 ? 'row-reverse' : 'row'} alignItems="start">
@ -162,7 +163,7 @@ export const MegaMenu = memo(
</Stack>
))}
</ul>
</CustomScrollbar>
</ScrollContainer>
</nav>
</div>
);

@ -120,8 +120,6 @@ beforeEach(() => {
]);
});
afterEach(() => jest.resetAllMocks());
describe('Silences', () => {
it(
'loads and shows silences',

@ -185,19 +185,19 @@ describe('SpanFilters', () => {
jest.advanceTimersByTime(1000);
await waitFor(() => {
const container = screen.getByText('TagKey0').parentElement?.parentElement?.parentElement;
expect(container?.childNodes[0].textContent).toBe('ProcessKey0');
expect(container?.childNodes[1].textContent).toBe('ProcessKey1');
expect(container?.childNodes[2].textContent).toBe('TagKey0');
expect(container?.childNodes[3].textContent).toBe('TagKey1');
expect(container?.childNodes[4].textContent).toBe('id');
expect(container?.childNodes[5].textContent).toBe('kind');
expect(container?.childNodes[6].textContent).toBe('library.name');
expect(container?.childNodes[7].textContent).toBe('library.version');
expect(container?.childNodes[8].textContent).toBe('status');
expect(container?.childNodes[9].textContent).toBe('status.message');
expect(container?.childNodes[10].textContent).toBe('trace.state');
expect(container?.childNodes[11].textContent).toBe('LogKey0');
expect(container?.childNodes[12].textContent).toBe('LogKey1');
expect(container?.childNodes[1].textContent).toBe('ProcessKey0');
expect(container?.childNodes[2].textContent).toBe('ProcessKey1');
expect(container?.childNodes[3].textContent).toBe('TagKey0');
expect(container?.childNodes[4].textContent).toBe('TagKey1');
expect(container?.childNodes[5].textContent).toBe('id');
expect(container?.childNodes[6].textContent).toBe('kind');
expect(container?.childNodes[7].textContent).toBe('library.name');
expect(container?.childNodes[8].textContent).toBe('library.version');
expect(container?.childNodes[9].textContent).toBe('status');
expect(container?.childNodes[10].textContent).toBe('status.message');
expect(container?.childNodes[11].textContent).toBe('trace.state');
expect(container?.childNodes[12].textContent).toBe('LogKey0');
expect(container?.childNodes[13].textContent).toBe('LogKey1');
});
});

Loading…
Cancel
Save