Skip to content

Switch to VariableSizeList #27

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions __stories__/helpers/constants/options-data.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type { CityOption, PackageOption } from '../../types';

export const PACKAGE_OPTIONS: PackageOption[] = [
{ id: 1, name: 'react' },
{ id: 1, name: 'react', description: 'React is a JavaScript library for creating user interfaces'},
{ id: 2, name: 'react-dom' },
{ id: 3, name: 'reactstrap' },
{ id: 4, name: 'react-scripts' },
{ id: 4, name: 'react-scripts', description: 'Configuration and scripts for Create React App.' },
{ id: 5, name: 'react-window' }
];

Expand Down
14 changes: 14 additions & 0 deletions __stories__/helpers/styled/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -387,10 +387,24 @@ export const OptionContainer = styled.div`
flex-direction: row;
`;

export const OptionContent = styled.div`
height: 100%;
display: flex;
flex-direction: column;
`;

export const OptionName = styled.span`
color: #515151;
font-size: 1em;
font-weight: 600;
margin-left: 1px;
margin-bottom: 1.5px;
`;

export const OptionDescription = styled.span`
color: #515151;
font-size: 1em;
font-weight: 300;
margin-left: 1px;
margin-bottom: 1.5px;
`;
19 changes: 16 additions & 3 deletions __stories__/index.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ import {
CardBody,
OtherSpan,
OptionContainer,
OptionContent,
OptionName,
OptionDescription,
ReactSvg,
ChevronDownSvg,
MenuPortalElement,
Expand Down Expand Up @@ -296,7 +298,7 @@ export const Styling = () => {

const selectWrapperStyle = { marginTop: '1rem' };
const noteStyle = { fontSize: 'inherit', fontWeight: 700 };
const menuItemSize = selectedOption?.value === ThemeEnum.LARGE_TEXT ? 44 : 35;
const getMenuItemSize = () => selectedOption?.value === ThemeEnum.LARGE_TEXT ? 44 : 35;

const memoizedMarkupNode = useMemo(() => (
<CodeMarkup
Expand Down Expand Up @@ -394,7 +396,7 @@ export const Styling = () => {
isSearchable={false}
options={THEME_OPTIONS}
themeConfig={themeConfig}
menuItemSize={menuItemSize}
getMenuItemSize={getMenuItemSize}
initialValue={THEME_OPTIONS[0]}
onOptionChange={setSelectedOption}
/>
Expand Down Expand Up @@ -807,12 +809,22 @@ export const Advanced = () => {
<path {...REACT_SVG_PATH_PROPS} />
<circle {...REACT_SVG_CIRCLE_PROPS} />
</ReactSvg>
<OptionName>{option.name}</OptionName>
<OptionContent>
<OptionName>{option.name}</OptionName>
{option.description && <OptionDescription>{option.description}</OptionDescription>}
</OptionContent>
</OptionContainer>
),
[getIsOptionDisabled]
);

const getMenuItemSize = useCallback(
(index: number): number => (
PACKAGE_OPTIONS[index].description ? 70 : 35
),
[]
);

const customCaretIcon = useCallback(
({ menuOpen }): ReactNode => (
<ChevronDownSvg
Expand Down Expand Up @@ -876,6 +888,7 @@ export const Advanced = () => {
caretIcon={customCaretIcon}
getOptionValue={getOptionValue}
renderOptionLabel={renderOptionLabel}
getMenuItemSize={getMenuItemSize}
getIsOptionDisabled={getIsOptionDisabled}
/>
</SelectContainer>
Expand Down
1 change: 1 addition & 0 deletions __stories__/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ export type CityOption = Readonly<{
export type PackageOption = Readonly<{
id: number;
name: string;
description?: string;
}>;
4 changes: 2 additions & 2 deletions __tests__/MenuList.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ const createMenuListProps = (menuOptions: MenuOption[] = []): MenuListProps => {
width: '100%',
renderOptionLabel,
focusedOptionIndex,
fixedSizeListRef: null,
variableSizeListRef: null,
itemKeySelector: undefined,
height: MENU_MAX_HEIGHT_DEFAULT,
loadingMsg: LOADING_MSG_DEFAULT,
itemSize: MENU_ITEM_SIZE_DEFAULT,
getItemSize: () => MENU_ITEM_SIZE_DEFAULT,
noOptionsMsg: NO_OPTIONS_MSG_DEFAULT
};
};
Expand Down
18 changes: 10 additions & 8 deletions src/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import {
import styled, { css, ThemeProvider } from 'styled-components';
import { Menu, Value, AriaLiveRegion, AutosizeInput, IndicatorIcons } from './components';

import type { FixedSizeList } from 'react-window';
import type { VariableSizeList } from 'react-window';
import type { DefaultTheme } from 'styled-components';
import type {
Ref,
Expand Down Expand Up @@ -111,7 +111,7 @@ export type SelectProps = Readonly<{
isDisabled?: boolean;
placeholder?: string;
menuWidth?: ReactText;
menuItemSize?: number;
getMenuItemSize?: (index: number) => number;
isClearable?: boolean;
lazyLoadMenu?: boolean;
options?: OptionData[];
Expand Down Expand Up @@ -281,7 +281,7 @@ const Select = forwardRef<SelectRef, SelectProps>((
loadingMsg = LOADING_MSG_DEFAULT,
placeholder = PLACEHOLDER_DEFAULT,
noOptionsMsg = NO_OPTIONS_MSG_DEFAULT,
menuItemSize = MENU_ITEM_SIZE_DEFAULT,
getMenuItemSize = () => MENU_ITEM_SIZE_DEFAULT,
menuMaxHeight = MENU_MAX_HEIGHT_DEFAULT
},
ref: Ref<SelectRef>
Expand All @@ -294,7 +294,7 @@ const Select = forwardRef<SelectRef, SelectProps>((
const onOptionChangeIsFunc = useRef<boolean>(isFunction(onOptionChange));

// DOM element refs
const listRef = useRef<FixedSizeList | null>(null);
const listRef = useRef<VariableSizeList | null>(null);
const menuRef = useRef<HTMLDivElement | null>(null);
const inputRef = useRef<HTMLInputElement | null>(null);
const controlRef = useRef<HTMLDivElement | null>(null);
Expand Down Expand Up @@ -343,15 +343,17 @@ const Select = forwardRef<SelectRef, SelectProps>((
hideSelectedOptions
);

const menuItemSizes = menuOptions.map((_, i) => getMenuItemSize(i))
const menuSize = menuItemSizes.reduce((a, b) => a + b, 0)

// Custom hook abstraction that handles calculating menuHeightCalc (defaults to menuMaxHeight) / handles executing callbacks/logic on menuOpen state change.
const [menuStyleTop, menuHeightCalc] = useMenuPositioner(
menuRef,
controlRef,
menuOpen,
menuPosition,
menuItemSize,
menuMaxHeight,
menuOptions.length,
menuSize,
!!menuPortalTarget,
onMenuOpen,
onMenuClose,
Expand Down Expand Up @@ -801,10 +803,10 @@ const Select = forwardRef<SelectRef, SelectProps>((
isLoading={isLoading}
menuTop={menuStyleTop}
height={menuHeightCalc}
itemSize={menuItemSize}
getItemSize={getMenuItemSize}
loadingMsg={loadingMsg}
menuOptions={menuOptions}
fixedSizeListRef={listRef}
variableSizeListRef={listRef}
noOptionsMsg={noOptionsMsg}
selectOption={selectOption}
direction={menuItemDirection}
Expand Down
14 changes: 7 additions & 7 deletions src/components/Menu/MenuList.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useMemo, Fragment } from 'react';
import Option from './Option';
import styled from 'styled-components';
import { FixedSizeList } from 'react-window';
import { VariableSizeList } from 'react-window';
import { isArrayWithLength } from '../../utils';

import type { MenuOption } from '../../Select';
Expand All @@ -11,7 +11,7 @@ import type { ItemData, RenderLabelCallback, SelectedOption } from '../../types'

export type MenuListProps = Readonly<{
height: number;
itemSize: number;
getItemSize: (index: number) => number;
loadingMsg: string;
isLoading?: boolean;
overscanCount?: number;
Expand All @@ -22,7 +22,7 @@ export type MenuListProps = Readonly<{
noOptionsMsg: string | null;
itemKeySelector?: ReactText;
renderOptionLabel: RenderLabelCallback;
fixedSizeListRef: MutableRefObject<FixedSizeList | null>;
variableSizeListRef: MutableRefObject<VariableSizeList | null>;
selectOption: (option: SelectedOption, isSelected?: boolean) => void;
}>;

Expand All @@ -38,7 +38,7 @@ const NoOptionsMsg = styled.div`
const MenuList: FunctionComponent<MenuListProps> = ({
width,
height,
itemSize,
getItemSize: itemSize,
direction,
isLoading,
loadingMsg,
Expand All @@ -47,7 +47,7 @@ const MenuList: FunctionComponent<MenuListProps> = ({
noOptionsMsg,
overscanCount,
itemKeySelector,
fixedSizeListRef,
variableSizeListRef: fixedSizeListRef,
renderOptionLabel,
focusedOptionIndex
}) => {
Expand All @@ -68,7 +68,7 @@ const MenuList: FunctionComponent<MenuListProps> = ({

return (
<Fragment>
<FixedSizeList
<VariableSizeList
width={width}
height={height}
itemKey={itemKey}
Expand All @@ -80,7 +80,7 @@ const MenuList: FunctionComponent<MenuListProps> = ({
itemCount={menuOptions.length}
>
{Option}
</FixedSizeList>
</VariableSizeList>
{!isArrayWithLength(menuOptions) && noOptionsMsg && (
<NoOptionsMsg>{noOptionsMsg}</NoOptionsMsg>
)}
Expand Down
5 changes: 2 additions & 3 deletions src/hooks/useMenuPositioner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,8 @@ const useMenuPositioner = (
controlRef: RefObject<HTMLElement | null>,
menuOpen: boolean,
menuPosition: MenuPositionEnum,
menuItemSize: number,
menuHeightDefault: number,
menuOptionsLength: number,
menuSize: number,
isMenuPortaled: boolean,
onMenuOpen?: CallbackFunction,
onMenuClose?: CallbackFunction,
Expand Down Expand Up @@ -78,7 +77,7 @@ const useMenuPositioner = (
}, [menuRef, menuOpen, menuHeightDefault, scrollMenuIntoView, menuScrollDuration, onMenuCloseRef, onMenuOpenRef]);

// Calculated menu height passed react-window; calculate MenuWrapper <div /> 'top' style prop if menu is positioned above control
const menuHeightCalc = Math.min(menuHeight, menuOptionsLength * menuItemSize);
const menuHeightCalc = Math.min(menuHeight, menuSize);

const menuStyleTop = isMenuTopPosition
? calculateMenuTop(menuHeightCalc, menuRef.current, controlRef.current)
Expand Down