in Dataset/JS/ReactSelect/index.tsx [1948:2126]
renderMenu() {
const {
Group,
GroupHeading,
Menu,
MenuList,
MenuPortal,
LoadingMessage,
NoOptionsMessage,
Option,
} = this.getComponents();
const { commonProps } = this;
const { focusedOption } = this.state;
const {
captureMenuScroll,
inputValue,
isLoading,
loadingMessage,
minMenuHeight,
maxMenuHeight,
menuIsOpen,
menuPlacement,
menuPosition,
menuPortalTarget,
menuShouldBlockScroll,
menuShouldScrollIntoView,
noOptionsMessage,
onMenuScrollToTop,
onMenuScrollToBottom,
} = this.props;
if (!menuIsOpen) return null;
// TODO: Internal Option Type here
const render = (props: CategorizedOption<Option>, id: string) => {
const { type, data, isDisabled, isSelected, label, value } = props;
const isFocused = focusedOption === data;
const onHover = isDisabled ? undefined : () => this.onOptionHover(data);
const onSelect = isDisabled ? undefined : () => this.selectOption(data);
const optionId = `${this.getElementId('option')}-${id}`;
const innerProps = {
id: optionId,
onClick: onSelect,
onMouseMove: onHover,
onMouseOver: onHover,
tabIndex: -1,
role: 'option',
'aria-selected': this.isAppleDevice ? undefined : isSelected, // is not supported on Apple devices
};
return (
<Option
{...commonProps}
innerProps={innerProps}
data={data}
isDisabled={isDisabled}
isSelected={isSelected}
key={optionId}
label={label}
type={type}
value={value}
isFocused={isFocused}
innerRef={isFocused ? this.getFocusedOptionRef : undefined}
>
{this.formatOptionLabel(props.data, 'menu')}
</Option>
);
};
let menuUI: ReactNode;
if (this.hasOptions()) {
menuUI = this.getCategorizedOptions().map((item) => {
if (item.type === 'group') {
const { data, options, index: groupIndex } = item;
const groupId = `${this.getElementId('group')}-${groupIndex}`;
const headingId = `${groupId}-heading`;
return (
<Group
{...commonProps}
key={groupId}
data={data}
options={options}
Heading={GroupHeading}
headingProps={{
id: headingId,
data: item.data,
}}
label={this.formatGroupLabel(item.data)}
>
{item.options.map((option) =>
render(option, `${groupIndex}-${option.index}`)
)}
</Group>
);
} else if (item.type === 'option') {
return render(item, `${item.index}`);
}
});
} else if (isLoading) {
const message = loadingMessage({ inputValue });
if (message === null) return null;
menuUI = <LoadingMessage {...commonProps}>{message}</LoadingMessage>;
} else {
const message = noOptionsMessage({ inputValue });
if (message === null) return null;
menuUI = <NoOptionsMessage {...commonProps}>{message}</NoOptionsMessage>;
}
const menuPlacementProps = {
minMenuHeight,
maxMenuHeight,
menuPlacement,
menuPosition,
menuShouldScrollIntoView,
};
const menuElement = (
<MenuPlacer {...commonProps} {...menuPlacementProps}>
{({ ref, placerProps: { placement, maxHeight } }) => (
<Menu
{...commonProps}
{...menuPlacementProps}
innerRef={ref}
innerProps={{
onMouseDown: this.onMenuMouseDown,
onMouseMove: this.onMenuMouseMove,
}}
isLoading={isLoading}
placement={placement}
>
<ScrollManager
captureEnabled={captureMenuScroll}
onTopArrive={onMenuScrollToTop}
onBottomArrive={onMenuScrollToBottom}
lockEnabled={menuShouldBlockScroll}
>
{(scrollTargetRef) => (
<MenuList
{...commonProps}
innerRef={(instance) => {
this.getMenuListRef(instance);
scrollTargetRef(instance);
}}
innerProps={{
role: 'listbox',
'aria-multiselectable': commonProps.isMulti,
id: this.getElementId('listbox'),
}}
isLoading={isLoading}
maxHeight={maxHeight}
focusedOption={focusedOption}
>
{menuUI}
</MenuList>
)}
</ScrollManager>
</Menu>
)}
</MenuPlacer>
);
// positioning behaviour is almost identical for portalled and fixed,
// so we use the same component. the actual portalling logic is forked
// within the component based on `menuPosition`
return menuPortalTarget || menuPosition === 'fixed' ? (
<MenuPortal
{...commonProps}
appendTo={menuPortalTarget}
controlElement={this.controlRef}
menuPlacement={menuPlacement}
menuPosition={menuPosition}
>
{menuElement}
</MenuPortal>
) : (
menuElement
);
}