-
Notifications
You must be signed in to change notification settings - Fork 2
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
POC of react-window to virutalize selectfield options #87
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,7 +4,16 @@ import { mergeProps } from "@react-aria/utils"; | |
import { Item } from "@react-stately/collections"; | ||
import { ComboBoxState, useComboBoxState } from "@react-stately/combobox"; | ||
import { CollectionChildren, Node } from "@react-types/shared"; | ||
import React, { Fragment, InputHTMLAttributes, Key, MutableRefObject, ReactNode, useRef, useState } from "react"; | ||
import React, { | ||
CSSProperties, | ||
Fragment, | ||
InputHTMLAttributes, | ||
Key, | ||
MutableRefObject, | ||
ReactNode, | ||
useRef, | ||
useState, | ||
} from "react"; | ||
import { | ||
OverlayContainer, | ||
useButton, | ||
|
@@ -15,6 +24,7 @@ import { | |
useOverlay, | ||
useOverlayPosition, | ||
} from "react-aria"; | ||
import { VariableSizeList } from "react-window"; | ||
import { ErrorMessage } from "src/components/ErrorMessage"; | ||
import { HelperText } from "src/components/HelperText"; | ||
import { Icon } from "src/components/Icon"; | ||
|
@@ -350,27 +360,46 @@ function ListBoxPopup<T extends object>(props: ListBoxPopupProps<T>) { | |
width: getFieldWidth(compact), | ||
}; | ||
|
||
const Row = ({ data, index, style }: any) => { | ||
const item = data.items[index]; | ||
return <Option item={item} state={state} style={style} key={item.key} />; | ||
}; | ||
|
||
return ( | ||
<OverlayContainer> | ||
<div {...{ ...overlayProps, ...positionProps }} ref={popoverRef}> | ||
<ul | ||
css={{ | ||
...Css.mtPx(4).bgWhite.br4.w100.bshBasic.$, | ||
...Css.mtPx(4).bgWhite.br4.w100.bshBasic.overflowAuto.maxh("400px").$, | ||
"&:hover": Css.bshHover.$, | ||
}} | ||
ref={listBoxRef} | ||
{...listBoxProps} | ||
> | ||
{[...state.collection].map((item) => ( | ||
<Option key={item.key} item={item} state={state} /> | ||
))} | ||
<VariableSizeList | ||
itemData={{ items: [...state.collection], state }} | ||
itemSize={() => 42} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can have this figure out the size for us dynamically based on option itself... Whether that is based on the MenuItem type (if we fix those heights), or doing a render of the item to figure out its height. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When it figures it out for us, does it just look at the 1st item and assume that is the height of all items in the list? ...I assume that is what it's doing, but AFAICT a core assumption of the virtualized approach is known/consistent height across all items? |
||
height={Math.min(42 * state.collection.size, 400)} | ||
itemCount={state.collection.size} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just curious, it seems like with |
||
width={getFieldWidth(compact)} | ||
> | ||
{Row} | ||
</VariableSizeList> | ||
</ul> | ||
</div> | ||
</OverlayContainer> | ||
); | ||
} | ||
|
||
function Option<T extends object>({ item, state }: { item: Node<T>; state: ComboBoxState<T> }) { | ||
function Option<T extends object>({ | ||
item, | ||
state, | ||
style, | ||
}: { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fwiw once the destructure-in-params starts to wrap like this, I think the |
||
item: Node<T>; | ||
state: ComboBoxState<T>; | ||
style: CSSProperties; | ||
}) { | ||
const ref = useRef<HTMLLIElement>(null); | ||
const isDisabled = state.disabledKeys.has(item.key); | ||
const isSelected = state.selectionManager.isSelected(item.key); | ||
|
@@ -399,10 +428,11 @@ function Option<T extends object>({ item, state }: { item: Node<T>; state: Combo | |
{...optionProps} | ||
{...hoverProps} | ||
ref={ref as any} | ||
style={style} | ||
css={{ | ||
...Css.df.itemsCenter.justifyBetween.py1.px2.mh("42px").cursorPointer.gray900.sm.$, | ||
...(isHovered ? Css.bgGray100.$ : {}), | ||
...(isFocused ? Css.add("boxShadow", `0 0 0 1px ${Palette.LightBlue700}`).$ : {}), | ||
...(isFocused ? Css.add("boxShadow", `inset 0 0 0 1px ${Palette.LightBlue700}`).$ : {}), | ||
}} | ||
> | ||
{item.rendered} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this needs to be a top-level function, b/c as is the "Row function" will get re-created on each re-render of
ListBoxPopup
and so look like a brand new component to react. ...I think. Basing this assertion on this ticket that I wandered by:bvaughn/react-window#302
"Inline item renderers incur a high cost. Because their "type" (the function definition) gets recreated each time the parent component renders, React deeply unmounts and remounts their rendered tree. This means that docs need to teach people not to use them even though they're often more convenient."