mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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
This commit is contained in:
parent
f97b7858b4
commit
b6b75e919b
@ -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…
Reference in New Issue
Block a user