mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Combobox: Use ScrollContainer
to show scroll indicators (#95962)
Add CustomScrollbar
This commit is contained in:
parent
06bdfe8e96
commit
a279220d74
@ -42,6 +42,9 @@ const meta: Meta<PropsAndCustomArgs> = {
|
|||||||
{ label: '1', value: 1 },
|
{ label: '1', value: 1 },
|
||||||
{ label: '2', value: 2 },
|
{ label: '2', value: 2 },
|
||||||
{ label: '3', value: 3 },
|
{ label: '3', value: 3 },
|
||||||
|
{ label: '4', value: 4 },
|
||||||
|
{ label: '5', value: 5 },
|
||||||
|
{ label: '6', value: 6 },
|
||||||
],
|
],
|
||||||
value: 'banana',
|
value: 'banana',
|
||||||
},
|
},
|
||||||
|
@ -10,6 +10,7 @@ import { Icon } from '../Icon/Icon';
|
|||||||
import { AutoSizeInput } from '../Input/AutoSizeInput';
|
import { AutoSizeInput } from '../Input/AutoSizeInput';
|
||||||
import { Input, Props as InputProps } from '../Input/Input';
|
import { Input, Props as InputProps } from '../Input/Input';
|
||||||
import { Stack } from '../Layout/Stack/Stack';
|
import { Stack } from '../Layout/Stack/Stack';
|
||||||
|
import { ScrollContainer } from '../ScrollContainer/ScrollContainer';
|
||||||
import { Text } from '../Text/Text';
|
import { Text } from '../Text/Text';
|
||||||
|
|
||||||
import { getComboboxStyles } from './getComboboxStyles';
|
import { getComboboxStyles } from './getComboboxStyles';
|
||||||
@ -132,7 +133,7 @@ export const Combobox = <T extends string | number>({
|
|||||||
|
|
||||||
const virtualizerOptions = {
|
const virtualizerOptions = {
|
||||||
count: items.length,
|
count: items.length,
|
||||||
getScrollElement: () => floatingRef.current,
|
getScrollElement: () => scrollRef.current,
|
||||||
estimateSize: () => OPTION_HEIGHT,
|
estimateSize: () => OPTION_HEIGHT,
|
||||||
overscan: 4,
|
overscan: 4,
|
||||||
};
|
};
|
||||||
@ -239,7 +240,7 @@ export const Combobox = <T extends string | number>({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { inputRef, floatingRef, floatStyles } = useComboboxFloat(items, rowVirtualizer.range, isOpen);
|
const { inputRef, floatingRef, floatStyles, scrollRef } = useComboboxFloat(items, rowVirtualizer.range, isOpen);
|
||||||
|
|
||||||
const onBlur = useCallback(() => {
|
const onBlur = useCallback(() => {
|
||||||
setInputValue(selectedItem?.label ?? value?.toString() ?? '');
|
setInputValue(selectedItem?.label ?? value?.toString() ?? '');
|
||||||
@ -311,46 +312,48 @@ export const Combobox = <T extends string | number>({
|
|||||||
'aria-labelledby': ariaLabelledBy,
|
'aria-labelledby': ariaLabelledBy,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{isOpen && !asyncError && (
|
<ScrollContainer showScrollIndicators maxHeight="inherit" ref={scrollRef}>
|
||||||
<ul style={{ height: rowVirtualizer.getTotalSize() }} className={styles.menuUlContainer}>
|
{isOpen && !asyncError && (
|
||||||
{rowVirtualizer.getVirtualItems().map((virtualRow) => {
|
<ul style={{ height: rowVirtualizer.getTotalSize() }} className={styles.menuUlContainer}>
|
||||||
return (
|
{rowVirtualizer.getVirtualItems().map((virtualRow) => {
|
||||||
<li
|
return (
|
||||||
key={`${items[virtualRow.index].value}-${virtualRow.index}`}
|
<li
|
||||||
data-index={virtualRow.index}
|
key={`${items[virtualRow.index].value}-${virtualRow.index}`}
|
||||||
className={cx(
|
data-index={virtualRow.index}
|
||||||
styles.option,
|
className={cx(
|
||||||
selectedItem && items[virtualRow.index].value === selectedItem.value && styles.optionSelected,
|
styles.option,
|
||||||
highlightedIndex === virtualRow.index && styles.optionFocused
|
selectedItem && items[virtualRow.index].value === selectedItem.value && styles.optionSelected,
|
||||||
)}
|
highlightedIndex === virtualRow.index && styles.optionFocused
|
||||||
style={{
|
|
||||||
height: virtualRow.size,
|
|
||||||
transform: `translateY(${virtualRow.start}px)`,
|
|
||||||
}}
|
|
||||||
{...getItemProps({
|
|
||||||
item: items[virtualRow.index],
|
|
||||||
index: virtualRow.index,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<div className={styles.optionBody}>
|
|
||||||
<span className={styles.optionLabel}>
|
|
||||||
{items[virtualRow.index].label ?? items[virtualRow.index].value}
|
|
||||||
</span>
|
|
||||||
{items[virtualRow.index].description && (
|
|
||||||
<span className={styles.optionDescription}>{items[virtualRow.index].description}</span>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
style={{
|
||||||
</li>
|
height: virtualRow.size,
|
||||||
);
|
transform: `translateY(${virtualRow.start}px)`,
|
||||||
})}
|
}}
|
||||||
</ul>
|
{...getItemProps({
|
||||||
)}
|
item: items[virtualRow.index],
|
||||||
{asyncError && (
|
index: virtualRow.index,
|
||||||
<Stack justifyContent="center" alignItems="center" height={8}>
|
})}
|
||||||
<Icon name="exclamation-triangle" size="md" className={styles.warningIcon} />
|
>
|
||||||
<Text color="secondary">{t('combobox.async.error', 'An error occurred while loading options.')}</Text>
|
<div className={styles.optionBody}>
|
||||||
</Stack>
|
<span className={styles.optionLabel}>
|
||||||
)}
|
{items[virtualRow.index].label ?? items[virtualRow.index].value}
|
||||||
|
</span>
|
||||||
|
{items[virtualRow.index].description && (
|
||||||
|
<span className={styles.optionDescription}>{items[virtualRow.index].description}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
{asyncError && (
|
||||||
|
<Stack justifyContent="center" alignItems="center" height={8}>
|
||||||
|
<Icon name="exclamation-triangle" size="md" className={styles.warningIcon} />
|
||||||
|
<Text color="secondary">{t('combobox.async.error', 'An error occurred while loading options.')}</Text>
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
</ScrollContainer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -18,7 +18,6 @@ export const getComboboxStyles = (theme: GrafanaTheme2) => {
|
|||||||
background: theme.components.dropdown.background,
|
background: theme.components.dropdown.background,
|
||||||
boxShadow: theme.shadows.z3,
|
boxShadow: theme.shadows.z3,
|
||||||
zIndex: theme.zIndex.dropdown,
|
zIndex: theme.zIndex.dropdown,
|
||||||
overflowY: 'auto',
|
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
}),
|
}),
|
||||||
menuUlContainer: css({
|
menuUlContainer: css({
|
||||||
|
@ -23,6 +23,7 @@ export const useComboboxFloat = (
|
|||||||
) => {
|
) => {
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
const floatingRef = useRef<HTMLDivElement>(null);
|
const floatingRef = useRef<HTMLDivElement>(null);
|
||||||
|
const scrollRef = useRef<HTMLDivElement>(null);
|
||||||
const [popoverMaxSize, setPopoverMaxSize] = useState<{ width: number; height: number } | undefined>(undefined);
|
const [popoverMaxSize, setPopoverMaxSize] = useState<{ width: number; height: number } | undefined>(undefined);
|
||||||
|
|
||||||
const scrollbarWidth = useMemo(() => getScrollbarWidth(), []);
|
const scrollbarWidth = useMemo(() => getScrollbarWidth(), []);
|
||||||
@ -79,7 +80,7 @@ export const useComboboxFloat = (
|
|||||||
maxHeight: popoverMaxSize?.height,
|
maxHeight: popoverMaxSize?.height,
|
||||||
};
|
};
|
||||||
|
|
||||||
return { inputRef, floatingRef, floatStyles };
|
return { inputRef, floatingRef, scrollRef, floatStyles };
|
||||||
};
|
};
|
||||||
|
|
||||||
// Creates a temporary div with a scrolling inner div to calculate the width of the scrollbar
|
// Creates a temporary div with a scrolling inner div to calculate the width of the scrollbar
|
||||||
|
Loading…
Reference in New Issue
Block a user