-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Depart away from react-windowed-select
In this release, we roll our own windowing inside of react-select. This is mainly done because react-windowed-select types are outdated and causing many downstream issues for the Select and AsyncSelect users.
- Loading branch information
1 parent
a2c9e9a
commit cdd03b9
Showing
15 changed files
with
682 additions
and
383 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -123,6 +123,7 @@ | |
"jscodeshift": "^0.11.0", | ||
"mockdate": "^3.0.2", | ||
"plop": "^2.4.0", | ||
"prettier": "^3.4.2", | ||
"react": "17.0.2", | ||
"react-color": "^2.18.1", | ||
"react-dom": "17.0.2", | ||
|
@@ -151,6 +152,7 @@ | |
"@styled-system/prop-types": "^5.1.4", | ||
"@styled-system/theme-get": "^5.1.2", | ||
"@types/react-router-dom": "5.3.0", | ||
"@types/react-window": "^1.8.8", | ||
"@types/styled-system": "5.1.22", | ||
"body-scroll-lock": "^3.1.5", | ||
"core-js": "3", | ||
|
@@ -169,6 +171,7 @@ | |
"react-popper-2": "npm:[email protected]", | ||
"react-resize-detector": "^9.1.0", | ||
"react-select": "^5.8.0", | ||
"react-window": "^1.8.11", | ||
"react-windowed-select": "^5.2.0", | ||
"smoothscroll-polyfill": "^0.4.4", | ||
"styled-system": "^5.1.4", | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
/* | ||
Copied as is from: https://github.com/jacobworrel/react-windowed-select/blob/master/src/MenuList.tsx | ||
MIT License | ||
Copyright (c) 2019 Jacob Worrel | ||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. | ||
*/ | ||
|
||
import * as React from "react"; | ||
import { ListChildComponentProps, VariableSizeList as List } from "react-window"; | ||
import { OptionProps, GroupBase } from "react-select"; | ||
import { createGetHeight, flattenGroupedChildren, getCurrentIndex } from "./lib"; | ||
|
||
interface Style extends React.CSSProperties { | ||
top: number; | ||
} | ||
|
||
interface ListChildProps extends ListChildComponentProps { | ||
style: Style; | ||
} | ||
|
||
interface OptionTypeBase { | ||
[key: string]: any; | ||
} | ||
|
||
function MenuList(props) { | ||
const children = React.useMemo(() => { | ||
const children = React.Children.toArray(props.children); | ||
|
||
const head = children[0] || {}; | ||
|
||
if (React.isValidElement<OptionProps<OptionTypeBase, boolean, GroupBase<OptionTypeBase>>>(head)) { | ||
const { props: { data: { options = [] } = {} } = {} } = head; | ||
const groupedChildrenLength = options.length; | ||
const isGrouped = groupedChildrenLength > 0; | ||
const flattenedChildren = isGrouped && flattenGroupedChildren(children); | ||
|
||
return isGrouped ? flattenedChildren : children; | ||
} else { | ||
return []; | ||
} | ||
}, [props.children]); | ||
|
||
const { getStyles } = props; | ||
const groupHeadingStyles = getStyles("groupHeading", props); | ||
const loadingMsgStyles = getStyles("loadingMessage", props); | ||
const noOptionsMsgStyles = getStyles("noOptionsMessage", props); | ||
const optionStyles = getStyles("option", props); | ||
const getHeight = createGetHeight({ | ||
groupHeadingStyles, | ||
noOptionsMsgStyles, | ||
optionStyles, | ||
loadingMsgStyles, | ||
}); | ||
|
||
const heights = React.useMemo(() => children.map(getHeight), [children]); | ||
const currentIndex = React.useMemo(() => getCurrentIndex(children), [children]); | ||
|
||
const itemCount = children.length; | ||
|
||
const [measuredHeights, setMeasuredHeights] = React.useState({}); | ||
|
||
// calc menu height | ||
const { maxHeight, paddingBottom = 0, paddingTop = 0, ...menuListStyle } = getStyles("menuList", props); | ||
const totalHeight = React.useMemo(() => { | ||
return heights.reduce((sum, height, idx) => { | ||
if (measuredHeights[idx]) { | ||
return sum + measuredHeights[idx]; | ||
} else { | ||
return sum + height; | ||
} | ||
}, 0); | ||
}, [heights, measuredHeights]); | ||
const totalMenuHeight = totalHeight + paddingBottom + paddingTop; | ||
const menuHeight = Math.min(maxHeight, totalMenuHeight); | ||
const estimatedItemSize = Math.floor(totalHeight / itemCount); | ||
|
||
const { innerRef, selectProps } = props; | ||
|
||
const { classNamePrefix, isMulti } = selectProps || {}; | ||
const list = React.useRef<List>(null); | ||
|
||
React.useEffect(() => { | ||
setMeasuredHeights({}); | ||
}, [props.children]); | ||
|
||
// method to pass to inner item to set this items outer height | ||
const setMeasuredHeight = ({ index, measuredHeight }) => { | ||
if (measuredHeights[index] !== undefined && measuredHeights[index] === measuredHeight) { | ||
return; | ||
} | ||
|
||
setMeasuredHeights((measuredHeights) => ({ | ||
...measuredHeights, | ||
[index]: measuredHeight, | ||
})); | ||
|
||
// this forces the list to rerender items after the item positions resizing | ||
if (list.current) { | ||
list.current.resetAfterIndex(index); | ||
} | ||
}; | ||
|
||
React.useEffect(() => { | ||
/** | ||
* enables scrolling on key down arrow | ||
*/ | ||
if (currentIndex >= 0 && list.current !== null) { | ||
list.current.scrollToItem(currentIndex); | ||
} | ||
}, [currentIndex, children, list]); | ||
|
||
return ( | ||
<List | ||
className={ | ||
classNamePrefix | ||
? `${classNamePrefix}__menu-list${isMulti ? ` ${classNamePrefix}__menu-list--is-multi` : ""}` | ||
: "" | ||
} | ||
style={menuListStyle} | ||
ref={list} | ||
outerRef={innerRef} | ||
estimatedItemSize={estimatedItemSize} | ||
innerElementType={React.forwardRef(({ style, ...rest }, ref) => ( | ||
<div | ||
ref={ref} | ||
style={{ | ||
...style, | ||
height: `${parseFloat(style.height) + paddingBottom + paddingTop}px`, | ||
}} | ||
{...rest} | ||
/> | ||
))} | ||
height={menuHeight} | ||
width="100%" | ||
itemCount={itemCount} | ||
itemData={children} | ||
itemSize={(index) => measuredHeights[index] || heights[index]} | ||
> | ||
{/*@ts-ignore*/} | ||
{({ data, index, style }: ListChildProps) => { | ||
return ( | ||
<div | ||
style={{ | ||
...style, | ||
top: `${parseFloat(style.top.toString()) + paddingTop}px`, | ||
}} | ||
> | ||
<MenuItem data={data[index]} index={index} setMeasuredHeight={setMeasuredHeight} /> | ||
</div> | ||
); | ||
}} | ||
</List> | ||
); | ||
} | ||
|
||
function MenuItem({ data, index, setMeasuredHeight }) { | ||
const ref = React.useRef<HTMLDivElement>(null); | ||
|
||
// using useLayoutEffect prevents bounciness of options of re-renders | ||
React.useLayoutEffect(() => { | ||
if (ref.current) { | ||
const measuredHeight = ref.current.getBoundingClientRect().height; | ||
|
||
setMeasuredHeight({ index, measuredHeight }); | ||
} | ||
}, [ref.current]); | ||
|
||
return ( | ||
<div key={`option-${index}`} ref={ref}> | ||
{data} | ||
</div> | ||
); | ||
} | ||
export default MenuList; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import React from "react"; | ||
import styled from "styled-components"; | ||
import { SelectOption, SelectOptionProps } from "./SelectOption"; | ||
import { NDSOption } from "./Select"; | ||
|
||
export const errorList = ["Error message 1", "Error message 2"]; | ||
|
||
export const options: NDSOption[] = [ | ||
{ value: "accepted", label: "Accepted" }, | ||
{ value: "assigned", label: "Assigned to a line" }, | ||
{ value: "hold", label: "On hold" }, | ||
{ value: "rejected", label: "Rejected" }, | ||
{ value: "open", label: "Open" }, | ||
{ value: "progress", label: "In progress" }, | ||
{ value: "quarantine", label: "In quarantine" }, | ||
]; | ||
|
||
export const partnerCompanyName = [ | ||
{ value: "2", label: "PCN2 12387387484895884957848576867587685780" }, | ||
{ value: "4", label: "PCN4 12387387484895884957848576867587685780" }, | ||
{ value: "1", label: "PCN1 12387387484895884957848576867587685780" }, | ||
{ value: "9", label: "PCN9 12387387484895884957848576867587685780" }, | ||
{ value: "7", label: "PCN7 12387387484895884957848576867587685780" }, | ||
{ value: "6", label: "PCN6 12387387484895884957848576867587685780" }, | ||
{ value: "3", label: "PCN3 12387387484895884957848576867587685780e" }, | ||
]; | ||
|
||
export const wrappingOptions = [ | ||
{ | ||
value: "onestring", | ||
label: | ||
"Onelongstringonelongstringonelongstringonelongstringonelongstringonelongstringonelongstringonelongstringonelongstringonelongstringonelongstring", | ||
}, | ||
{ | ||
value: "manywords", | ||
label: | ||
"Many words many words many words many words many words many words many words many words many words many words many words many words many words", | ||
}, | ||
]; | ||
|
||
export const PCNList = [ | ||
{ value: "2", label: "PCN2" }, | ||
{ value: "4", label: "PCN4" }, | ||
{ value: "1", label: "PCN1" }, | ||
{ value: "9", label: "PCN9" }, | ||
]; | ||
|
||
export const getPhotos = async () => { | ||
// returns 5000 items | ||
const data = await fetch("https://jsonplaceholder.typicode.com/photos"); | ||
const json = await data.json(); | ||
return json.map(({ title, id }) => ({ | ||
label: title, | ||
value: id, | ||
})); | ||
}; | ||
|
||
const Indicator = styled.span(() => ({ | ||
borderRadius: "25%", | ||
background: "green", | ||
lineHeight: "0", | ||
display: "inline-block", | ||
width: "10px", | ||
height: "10px", | ||
marginRight: "5px", | ||
})); | ||
|
||
export const CustomOption = ({ children, ...props }: SelectOptionProps) => { | ||
const newChildren = ( | ||
<> | ||
<Indicator /> | ||
{children} | ||
</> | ||
); | ||
return <SelectOption {...props}>{newChildren}</SelectOption>; | ||
}; |
Oops, something went wrong.