MultiCombobox: Add floating menu styles (#98258)

Add floating menu styles
pull/98284/head
Tobias Skarhed 7 months ago committed by GitHub
parent 746bde0fac
commit e640b3c179
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      packages/grafana-ui/src/components/Combobox/Combobox.story.tsx
  2. 2
      packages/grafana-ui/src/components/Combobox/Combobox.tsx
  3. 61
      packages/grafana-ui/src/components/Combobox/MultiCombobox.tsx
  4. 6
      packages/grafana-ui/src/components/Combobox/useComboboxFloat.ts

@ -26,7 +26,7 @@ const meta: Meta<PropsAndCustomArgs> = {
args: { args: {
loading: undefined, loading: undefined,
invalid: undefined, invalid: undefined,
width: undefined, width: 20,
isClearable: false, isClearable: false,
placeholder: 'Select an option...', placeholder: 'Select an option...',
options: [ options: [

@ -354,7 +354,7 @@ export const Combobox = <T extends string | number>(props: ComboboxProps<T>) =>
}, },
}); });
const { inputRef, floatingRef, floatStyles, scrollRef } = useComboboxFloat(items, rowVirtualizer.range, isOpen); const { inputRef, floatingRef, floatStyles, scrollRef } = useComboboxFloat(items, isOpen);
const isAutoSize = width === 'auto'; const isAutoSize = width === 'auto';

@ -6,13 +6,16 @@ import { useStyles2 } from '../../themes';
import { Checkbox } from '../Forms/Checkbox'; import { Checkbox } from '../Forms/Checkbox';
import { Box } from '../Layout/Box/Box'; import { Box } from '../Layout/Box/Box';
import { Portal } from '../Portal/Portal'; import { Portal } from '../Portal/Portal';
import { ScrollContainer } from '../ScrollContainer/ScrollContainer';
import { Text } from '../Text/Text'; import { Text } from '../Text/Text';
import { Tooltip } from '../Tooltip'; import { Tooltip } from '../Tooltip';
import { ComboboxOption, ComboboxBaseProps, AutoSizeConditionals, itemToString } from './Combobox'; import { ComboboxOption, ComboboxBaseProps, AutoSizeConditionals, itemToString } from './Combobox';
import { OptionListItem } from './OptionListItem'; import { OptionListItem } from './OptionListItem';
import { ValuePill } from './ValuePill'; import { ValuePill } from './ValuePill';
import { getComboboxStyles } from './getComboboxStyles';
import { getMultiComboboxStyles } from './getMultiComboboxStyles'; import { getMultiComboboxStyles } from './getMultiComboboxStyles';
import { useComboboxFloat } from './useComboboxFloat';
import { useMeasureMulti } from './useMeasureMulti'; import { useMeasureMulti } from './useMeasureMulti';
interface MultiComboboxBaseProps<T extends string | number> extends Omit<ComboboxBaseProps<T>, 'value' | 'onChange'> { interface MultiComboboxBaseProps<T extends string | number> extends Omit<ComboboxBaseProps<T>, 'value' | 'onChange'> {
@ -35,9 +38,13 @@ export const MultiCombobox = <T extends string | number>(props: MultiComboboxPro
return getSelectedItemsFromValue<T>(value, options); return getSelectedItemsFromValue<T>(value, options);
}, [value, options, isAsync]); }, [value, options, isAsync]);
const styles = useStyles2(getComboboxStyles);
const [items, _baseSetItems] = useState(isAsync ? [] : options); const [items, _baseSetItems] = useState(isAsync ? [] : options);
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const { inputRef: containerRef, floatingRef, floatStyles, scrollRef } = useComboboxFloat(items, isOpen);
const multiStyles = useStyles2(getMultiComboboxStyles, isOpen); const multiStyles = useStyles2(getMultiComboboxStyles, isOpen);
const { measureRef, suffixMeasureRef, shownItems } = useMeasureMulti(selectedItems, width); const { measureRef, suffixMeasureRef, shownItems } = useMeasureMulti(selectedItems, width);
@ -124,7 +131,7 @@ export const MultiCombobox = <T extends string | number>(props: MultiComboboxPro
const visibleItems = isOpen ? selectedItems : selectedItems.slice(0, shownItems); const visibleItems = isOpen ? selectedItems : selectedItems.slice(0, shownItems);
return ( return (
<div> <div ref={containerRef}>
<div <div
style={{ width: width === 'auto' ? undefined : width }} style={{ width: width === 'auto' ? undefined : width }}
className={multiStyles.wrapper} className={multiStyles.wrapper}
@ -175,31 +182,37 @@ export const MultiCombobox = <T extends string | number>(props: MultiComboboxPro
/> />
</span> </span>
</div> </div>
<div {...getMenuProps()}> <Portal>
<Portal> <div
className={cx(styles.menu, !isOpen && styles.menuClosed)}
style={{ ...floatStyles }}
{...getMenuProps({ ref: floatingRef })}
>
{isOpen && ( {isOpen && (
<div> <ScrollContainer showScrollIndicators maxHeight="inherit" ref={scrollRef}>
{items.map((item, index) => { <ul>
const itemProps = getItemProps({ item, index }); {items.map((item, index) => {
const isSelected = isOptionSelected(item); const itemProps = getItemProps({ item, index });
const id = 'multicombobox-option-' + item.value.toString(); const isSelected = isOptionSelected(item);
return ( const id = 'multicombobox-option-' + item.value.toString();
<li return (
key={item.value} <li
{...itemProps} key={item.value}
style={highlightedIndex === index ? { backgroundColor: 'blue' } : {}} {...itemProps}
> style={highlightedIndex === index ? { backgroundColor: 'blue' } : {}}
{' '} >
{/* Add styling with virtualization */} {' '}
<Checkbox key={id} value={isSelected} aria-labelledby={id} /> {/* Add styling with virtualization */}
<OptionListItem option={item} id={id} /> <Checkbox key={id} value={isSelected} aria-labelledby={id} />
</li> <OptionListItem option={item} id={id} />
); </li>
})} );
</div> })}
</ul>
</ScrollContainer>
)} )}
</Portal> </div>
</div> </Portal>
</div> </div>
); );
}; };

@ -18,11 +18,7 @@ const WIDTH_CALCULATION_LIMIT_ITEMS = 100_000;
// Clearance around the popover to prevent it from being too close to the edge of the viewport // Clearance around the popover to prevent it from being too close to the edge of the viewport
const POPOVER_PADDING = 16; const POPOVER_PADDING = 16;
export const useComboboxFloat = ( export const useComboboxFloat = (items: Array<ComboboxOption<string | number>>, isOpen: boolean) => {
items: Array<ComboboxOption<string | number>>,
range: { startIndex: number; endIndex: number } | null,
isOpen: boolean
) => {
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 scrollRef = useRef<HTMLDivElement>(null);

Loading…
Cancel
Save