Select: Select menus now properly scroll during keyboard navigation (#41917)

* Select: Select menus now properly scroll when navigating with the keyboard

* Remove this unnecessary children declaration in the interface

* Guard this with an if statement to avoid the nullish coalescing

* Don't need the optional chaining if we're guarding with an if
pull/42045/head
Ashley Harrison 4 years ago committed by GitHub
parent f97b7858b4
commit b6b75e919b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.tsx
  2. 73
      packages/grafana-ui/src/components/Select/SelectMenu.tsx

@ -1,4 +1,4 @@
import React, { FC, useCallback, useEffect, useRef } from 'react';
import React, { FC, RefCallback, useCallback, useEffect, useRef } from 'react';
import { isNil } from 'lodash';
import classNames from 'classnames';
import { css } from '@emotion/css';
@ -16,6 +16,7 @@ interface Props {
hideTracksWhenNotNeeded?: boolean;
hideHorizontalTrack?: boolean;
hideVerticalTrack?: boolean;
scrollRefCallback?: RefCallback<HTMLDivElement>;
scrollTop?: number;
setScrollTop?: (position: ScrollbarPosition) => void;
autoHeightMin?: number | string;
@ -35,11 +36,17 @@ export const CustomScrollbar: FC<Props> = ({
hideTracksWhenNotNeeded = false,
hideHorizontalTrack,
hideVerticalTrack,
scrollRefCallback,
updateAfterMountMs,
scrollTop,
children,
}) => {
const ref = useRef<Scrollbars>(null);
const ref = useRef<Scrollbars & { view: HTMLDivElement }>(null);
useEffect(() => {
if (ref.current) {
scrollRefCallback?.(ref.current.view);
}
}, [ref, scrollRefCallback]);
const styles = useStyles2(getStyles);
const updateScroll = () => {

@ -1,4 +1,4 @@
import React from 'react';
import React, { FC, RefCallback } from 'react';
import { useTheme2 } from '../../themes/ThemeContext';
import { getSelectStyles } from './getSelectStyles';
import { cx } from '@emotion/css';
@ -9,23 +9,22 @@ import { IconName } from '../../types';
interface SelectMenuProps {
maxHeight: number;
innerRef: React.Ref<any>;
innerRef: RefCallback<HTMLDivElement>;
innerProps: {};
}
export const SelectMenu = React.forwardRef<HTMLDivElement, React.PropsWithChildren<SelectMenuProps>>((props, ref) => {
export const SelectMenu: FC<SelectMenuProps> = ({ children, maxHeight, innerRef, innerProps }) => {
const theme = useTheme2();
const styles = getSelectStyles(theme);
const { children, maxHeight, innerRef, innerProps } = props;
return (
<div {...innerProps} className={styles.menu} ref={innerRef} style={{ maxHeight }} aria-label="Select options menu">
<CustomScrollbar autoHide={false} autoHeightMax="inherit" hideHorizontalTrack>
<div {...innerProps} className={styles.menu} style={{ maxHeight }} aria-label="Select options menu">
<CustomScrollbar scrollRefCallback={innerRef} autoHide={false} autoHeightMax="inherit" hideHorizontalTrack>
{children}
</CustomScrollbar>
</div>
);
});
};
SelectMenu.displayName = 'SelectMenu';
@ -34,38 +33,44 @@ interface SelectMenuOptionProps<T> {
isFocused: boolean;
isSelected: boolean;
innerProps: any;
innerRef: RefCallback<HTMLDivElement>;
renderOptionLabel?: (value: SelectableValue<T>) => JSX.Element;
data: SelectableValue<T>;
}
export const SelectMenuOptions = React.forwardRef<HTMLDivElement, React.PropsWithChildren<SelectMenuOptionProps<any>>>(
(props, ref) => {
const theme = useTheme2();
const styles = getSelectStyles(theme);
const { children, innerProps, data, renderOptionLabel, isSelected, isFocused } = props;
export const SelectMenuOptions: FC<SelectMenuOptionProps<any>> = ({
children,
data,
innerProps,
innerRef,
isFocused,
isSelected,
renderOptionLabel,
}) => {
const theme = useTheme2();
const styles = getSelectStyles(theme);
return (
<div
ref={ref}
className={cx(
styles.option,
isFocused && styles.optionFocused,
isSelected && styles.optionSelected,
data.isDisabled && styles.optionDisabled
)}
{...innerProps}
aria-label="Select option"
>
{data.icon && <Icon name={data.icon as IconName} className={styles.optionIcon} />}
{data.imgUrl && <img className={styles.optionImage} src={data.imgUrl} alt={data.label || data.value} />}
<div className={styles.optionBody}>
<span>{renderOptionLabel ? renderOptionLabel(data) : children}</span>
{data.description && <div className={styles.optionDescription}>{data.description}</div>}
{data.component && <data.component />}
</div>
return (
<div
ref={innerRef}
className={cx(
styles.option,
isFocused && styles.optionFocused,
isSelected && styles.optionSelected,
data.isDisabled && styles.optionDisabled
)}
{...innerProps}
aria-label="Select option"
>
{data.icon && <Icon name={data.icon as IconName} className={styles.optionIcon} />}
{data.imgUrl && <img className={styles.optionImage} src={data.imgUrl} alt={data.label || data.value} />}
<div className={styles.optionBody}>
<span>{renderOptionLabel ? renderOptionLabel(data) : children}</span>
{data.description && <div className={styles.optionDescription}>{data.description}</div>}
{data.component && <data.component />}
</div>
);
}
);
</div>
);
};
SelectMenuOptions.displayName = 'SelectMenuOptions';

Loading…
Cancel
Save