Make sure big lists of suggestions don't expand outside of the viewport (#33520)

* Add CustomScrollbar to data links suggestions

* Make sure to scroll to selected suggestion
This commit is contained in:
Oscar Kilhed 2021-04-30 09:52:34 +02:00 committed by GitHub
parent deb9ead72f
commit aaca022df6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 26 additions and 9 deletions

View File

@ -18,6 +18,7 @@ import { SCHEMA } from '../../utils/slate';
import { useStyles2 } from '../../themes'; import { useStyles2 } from '../../themes';
import { DataLinkBuiltInVars, GrafanaThemeV2, VariableOrigin, VariableSuggestion } from '@grafana/data'; import { DataLinkBuiltInVars, GrafanaThemeV2, VariableOrigin, VariableSuggestion } from '@grafana/data';
import { getInputStyles } from '../Input/Input'; import { getInputStyles } from '../Input/Input';
import CustomScrollbar from '../CustomScrollbar/CustomScrollbar';
const modulo = (a: number, n: number) => a - n * Math.floor(a / n); const modulo = (a: number, n: number) => a - n * Math.floor(a / n);
@ -81,6 +82,12 @@ export const DataLinkInput: React.FC<DataLinkInputProps> = memo(
const stateRef = useRef({ showingSuggestions, suggestions, suggestionsIndex, linkUrl, onChange }); const stateRef = useRef({ showingSuggestions, suggestions, suggestionsIndex, linkUrl, onChange });
stateRef.current = { showingSuggestions, suggestions, suggestionsIndex, linkUrl, onChange }; stateRef.current = { showingSuggestions, suggestions, suggestionsIndex, linkUrl, onChange };
// Used to get the height of the suggestion elements in order to scroll to them.
const activeRef = useRef<HTMLDivElement>(null);
const activeIndexPosition = useMemo(() => getElementPosition(activeRef.current, suggestionsIndex), [
suggestionsIndex,
]);
// SelectionReference is used to position the variables suggestion relatively to current DOM selection // SelectionReference is used to position the variables suggestion relatively to current DOM selection
const selectionRef = useMemo(() => new SelectionReference(), []); const selectionRef = useMemo(() => new SelectionReference(), []);
@ -172,12 +179,15 @@ export const DataLinkInput: React.FC<DataLinkInputProps> = memo(
{({ ref, style, placement }) => { {({ ref, style, placement }) => {
return ( return (
<div ref={ref} style={style} data-placement={placement}> <div ref={ref} style={style} data-placement={placement}>
<DataLinkSuggestions <CustomScrollbar scrollTop={activeIndexPosition} autoHeightMax="300px">
suggestions={stateRef.current.suggestions} <DataLinkSuggestions
onSuggestionSelect={onVariableSelect} activeRef={activeRef}
onClose={() => setShowingSuggestions(false)} suggestions={stateRef.current.suggestions}
activeIndex={suggestionsIndex} onSuggestionSelect={onVariableSelect}
/> onClose={() => setShowingSuggestions(false)}
activeIndex={suggestionsIndex}
/>
</CustomScrollbar>
</div> </div>
); );
}} }}
@ -208,3 +218,7 @@ export const DataLinkInput: React.FC<DataLinkInputProps> = memo(
); );
DataLinkInput.displayName = 'DataLinkInput'; DataLinkInput.displayName = 'DataLinkInput';
function getElementPosition(suggestionElement: HTMLElement | null, activeIndex: number) {
return (suggestionElement?.clientHeight ?? 0) * activeIndex;
}

View File

@ -7,6 +7,7 @@ import { List } from '../index';
import { useStyles2 } from '../../themes'; import { useStyles2 } from '../../themes';
interface DataLinkSuggestionsProps { interface DataLinkSuggestionsProps {
activeRef?: React.RefObject<HTMLDivElement>;
suggestions: VariableSuggestion[]; suggestions: VariableSuggestion[];
activeIndex: number; activeIndex: number;
onSuggestionSelect: (suggestion: VariableSuggestion) => void; onSuggestionSelect: (suggestion: VariableSuggestion) => void;
@ -23,7 +24,6 @@ const getStyles = (theme: GrafanaThemeV2) => {
`, `,
wrapper: css` wrapper: css`
background: ${theme.colors.background.primary}; background: ${theme.colors.background.primary};
z-index: 1;
width: 250px; width: 250px;
box-shadow: 0 5px 10px 0 ${theme.shadows.z1}; box-shadow: 0 5px 10px 0 ${theme.shadows.z1};
`, `,
@ -100,10 +100,11 @@ DataLinkSuggestions.displayName = 'DataLinkSuggestions';
interface DataLinkSuggestionsListProps extends DataLinkSuggestionsProps { interface DataLinkSuggestionsListProps extends DataLinkSuggestionsProps {
label: string; label: string;
activeIndexOffset: number; activeIndexOffset: number;
activeRef?: React.RefObject<HTMLDivElement>;
} }
const DataLinkSuggestionsList: React.FC<DataLinkSuggestionsListProps> = React.memo( const DataLinkSuggestionsList: React.FC<DataLinkSuggestionsListProps> = React.memo(
({ activeIndex, activeIndexOffset, label, onClose, onSuggestionSelect, suggestions }) => { ({ activeIndex, activeIndexOffset, label, onClose, onSuggestionSelect, suggestions, activeRef: selectedRef }) => {
const styles = useStyles2(getStyles); const styles = useStyles2(getStyles);
return ( return (
@ -112,9 +113,11 @@ const DataLinkSuggestionsList: React.FC<DataLinkSuggestionsListProps> = React.me
className={styles.list} className={styles.list}
items={suggestions} items={suggestions}
renderItem={(item, index) => { renderItem={(item, index) => {
const isActive = index + activeIndexOffset === activeIndex;
return ( return (
<div <div
className={cx(styles.item, index + activeIndexOffset === activeIndex && styles.activeItem)} className={cx(styles.item, isActive && styles.activeItem)}
ref={isActive ? selectedRef : undefined}
onClick={() => { onClick={() => {
onSuggestionSelect(item); onSuggestionSelect(item);
}} }}