diff --git a/.eslintrc b/.eslintrc index a1d9e0450..18adea489 100644 --- a/.eslintrc +++ b/.eslintrc @@ -28,6 +28,7 @@ } ], "react/display-name": "off", + "@typescript-eslint/explicit-module-boundary-types": "off", // rules below are to override nulogy config "react/jsx-filename-extension": [ 1, diff --git a/package.json b/package.json index 3da3f04cf..f78dedd63 100644 --- a/package.json +++ b/package.json @@ -142,6 +142,8 @@ "@nulogy/tokens": "^5.4.0", "@styled-system/prop-types": "^5.1.4", "@styled-system/theme-get": "^5.1.2", + "@types/react-window": "^1.8.8", + "@types/styled-system": "5.1.22", "body-scroll-lock": "^3.1.5", "core-js": "3", "create-react-context": "^0.3.0", @@ -160,11 +162,9 @@ "react-popper": "1.3.11", "react-popper-2": "npm:react-popper@2.2.4", "react-resize-detector": "^9.1.0", - "react-windowed-select": "^5.2.0", - "smoothscroll-polyfill": "^0.4.4", "react-select": "^5.8.0", - "styled-system": "^5.1.4", - "@types/styled-system": "5.1.22" + "react-window": "^1.8.10", + "styled-system": "^5.1.4" }, "husky": { "hooks": { diff --git a/rollup.config.js b/rollup.config.js index ab3f7b3fe..318acce18 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -35,7 +35,6 @@ const GLOBALS = { classnames: "t", "react-input-autosize": "AutosizeInput", "html-parse-stringify2": "HTML", - "smoothscroll-polyfill": "smoothscroll", "react-fast-compare": "isEqual", "path-to-regexp": "pathToRegexp", "react-is": "reactIs", @@ -54,7 +53,7 @@ const CORE_PLUGINS = [ commonjs({ /* include: include all items in node_modules folders (in entire monorepo) */ include: [/node_modules/], - /* namedExports: sometimes commonjs can't resolve named exports from certain libraries, + /* namedExports: sometimes commonjs can't resolve named exports from certain libraries, ex: import {exportName} from "package-name"; => exportName module not found in those cases, it needs to be added as ["package-name"]: "exportName" here */ namedExports: { diff --git a/src/AsyncSelect/AsyncSelect.tsx b/src/AsyncSelect/AsyncSelect.tsx index 34ffe9697..4b17faca8 100644 --- a/src/AsyncSelect/AsyncSelect.tsx +++ b/src/AsyncSelect/AsyncSelect.tsx @@ -128,7 +128,6 @@ const AsyncSelect = forwardRef( onMenuClose={onMenuClose} menuPosition={menuPosition} onInputChange={onInputChange} - theme={theme as any} components={{ Option: (props) => ( diff --git a/src/AsyncSelect/AsyncSelectComponents.tsx b/src/AsyncSelect/AsyncSelectComponents.tsx index 4c8725b6c..8f13e47e6 100644 --- a/src/AsyncSelect/AsyncSelectComponents.tsx +++ b/src/AsyncSelect/AsyncSelectComponents.tsx @@ -10,7 +10,7 @@ import { MultiValueProps, } from "react-select"; import { components, GroupBase } from "react-select"; -import { OptionProps } from "react-windowed-select"; +import { OptionProps } from "react-select"; import { ComponentSize, useComponentSize } from "../NDSProvider/ComponentSizeContext"; import { StyledOption } from "../Select/SelectOption"; diff --git a/src/Select/MenuList.tsx b/src/Select/MenuList.tsx new file mode 100644 index 000000000..cf606f194 --- /dev/null +++ b/src/Select/MenuList.tsx @@ -0,0 +1,193 @@ +/* + 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>>(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(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 ( + ( +
+ ))} + height={menuHeight} + width="100%" + itemCount={itemCount} + itemData={children} + itemSize={(index) => measuredHeights[index] || heights[index]} + > + {/*@ts-ignore*/} + {({ data, index, style }: ListChildProps) => { + return ( +
+ +
+ ); + }} + + ); +} + +function MenuItem({ data, index, setMeasuredHeight }) { + const ref = React.useRef(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 ( +
+ {data} +
+ ); +} +export default MenuList; diff --git a/src/Select/Select.spec.tsx b/src/Select/Select.spec.tsx index 74196891d..4f74d04ce 100644 --- a/src/Select/Select.spec.tsx +++ b/src/Select/Select.spec.tsx @@ -2,7 +2,7 @@ import React from "react"; import { fireEvent } from "@testing-library/react"; import { renderWithNDSProvider } from "../NDSProvider/renderWithNDSProvider.spec-utils"; import { selectOption } from "./Select.spec-utils"; -import { UsingRefToControlFocus, WithCustomProps, WithMultiselect, WithState } from "./Select.story"; +import { UsingRefToControlFocus, WithMultiselect, WithState } from "./Select.story"; import { Select } from "."; describe("select", () => { @@ -38,14 +38,6 @@ describe("select", () => { expect(container).toHaveTextContent("Three"); }); - it("passes along the custom props to custom components", () => { - const { container, queryByText } = renderWithNDSProvider(); - - selectOption("custom prop value", container, queryByText); - - expect(container).toHaveTextContent("custom prop value"); - }); - describe("with state", () => { it("clears the selected option", () => { const { container, queryByText } = renderWithNDSProvider(); diff --git a/src/Select/Select.story.fixture.tsx b/src/Select/Select.story.fixture.tsx new file mode 100644 index 000000000..184087666 --- /dev/null +++ b/src/Select/Select.story.fixture.tsx @@ -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 = ( + <> + + {children} + + ); + return {newChildren}; +}; diff --git a/src/Select/Select.story.tsx b/src/Select/Select.story.tsx index 802904603..dc72ca3ff 100644 --- a/src/Select/Select.story.tsx +++ b/src/Select/Select.story.tsx @@ -1,70 +1,22 @@ -import React, { useEffect, useState, useRef } from "react"; +import React, { useEffect, useRef, useState } from "react"; import { action } from "@storybook/addon-actions"; -import styled from "styled-components"; -import { text, boolean, select } from "@storybook/addon-knobs"; -import { GroupBase, OptionProps } from "react-windowed-select"; -import { Button, Heading2, Select, SelectOption } from "../index"; +import { boolean, select, text } from "@storybook/addon-knobs"; +import { PropsValue } from "react-select"; import { Box } from "../Box"; import { Flex } from "../Flex"; -import { NDSSelectProps } from "./Select"; - -const errorList = ["Error message 1", "Error message 2"]; - -const options = [ - { 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" }, -]; - -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" }, -]; - -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", - }, -]; - -const PCNList = [ - { value: "2", label: "PCN2" }, - { value: "4", label: "PCN4" }, - { value: "1", label: "PCN1" }, - { value: "9", label: "PCN9" }, -]; - -const getPhotos = async () => { - // returns 5000 items - const data = await fetch("https://jsonplaceholder.typicode.com/photos"); - const json = await data.json(); - const results = json.map(({ title, id }) => ({ - label: title, - value: id, - })); - return results; -}; - -const SelectWithManyOptions = >({ - multiselect, - labelText, -}: Pick, "multiselect" | "labelText">) => { +import { Button, Heading2 } from "../index"; +import Select, { NDSOptionValue, NDSSelectProps } from "./Select"; +import { + CustomOption, + getPhotos, + options, + partnerCompanyName, + wrappingOptions, + PCNList, + errorList, +} from "./Select.story.fixture"; + +const SelectWithManyOptions = ({ multiselect, labelText, ...props }: Partial) => { const [photoList, setPhotoList] = useState([]); const setOptions = async () => { @@ -76,35 +28,46 @@ const SelectWithManyOptions = ; + return - - - ); + clearSelection() { + this.setState({ selectedValue: "" }); + } + + render() { + const { selectedValue } = this.state; + return ( + + "No options"} - placeholder="Please select inventory status" - options={options} - components={{ - Option: CustomOption, - }} - multiselect - labelText="Inventory status" - menuPosition="fixed" - /> - - + + - - ); -}; diff --git a/src/Select/Select.tsx b/src/Select/Select.tsx index db67d1297..7aebbe90b 100644 --- a/src/Select/Select.tsx +++ b/src/Select/Select.tsx @@ -1,109 +1,71 @@ -import React, { ReactNode } from "react"; -import propTypes from "@styled-system/prop-types"; -import WindowedSelect, { GroupBase } from "react-windowed-select"; -import type { MenuPlacement, MenuPosition, Props as SelectProps } from "react-select"; +import React, { forwardRef, ReactNode, MutableRefObject } from "react"; +import Select from "react-select/base"; +import ReactSelect, { PropsValue } from "react-select"; +import type { GroupBase, Props } from "react-select"; import { useTranslation } from "react-i18next"; -import { ThemeContext } from "styled-components"; +import { useTheme } from "styled-components"; +import propTypes from "@styled-system/prop-types"; import { Field } from "../Form"; import { MaybeFieldLabel } from "../FieldLabel"; import { InlineValidation } from "../Validation"; +import customStyles from "../Select/customReactSelectStyles"; import { getSubset } from "../utils/subset"; import { ComponentSize, useComponentSize } from "../NDSProvider/ComponentSizeContext"; -import customStyles from "./customReactSelectStyles"; -import { SelectOption } from "./SelectOption"; - import { SelectControl, SelectMultiValue, SelectClearIndicator, SelectContainer, - SelectMenu, SelectInput, SelectDropdownIndicator, + SelectMenu, } from "./SelectComponents"; +import { SelectOption } from "./SelectOption"; +import MenuList from "./MenuList"; +import { calcOptionsLength, checkOptionsAreValid, extractValue, getReactSelectValue } from "./lib"; -type ReactSelectStateManager = { - state: { - value: any[]; - }; - setState: (prevState: any) => void; - blur: () => void; -}; - -// NOTE: We recreate these props as upstream doesn't export them. Note also that -// we have a default value for windowThreshold, therefore this param is optional. -interface WindowedSelectProps extends SelectProps { - windowThreshold?: number; -} +export type NDSOptionValue = string | number | boolean | null; -interface NDSOptionType { +export interface NDSOption { label: string; - value: unknown; + value: NDSOptionValue; } -interface CustomProps> { - autocomplete?: SelectProps["isSearchable"]; +type CustomProps> = { + autocomplete?: Props["isSearchable"]; labelText?: string; + size?: ComponentSize; requirementText?: string; helpText?: ReactNode; - disabled?: SelectProps["isDisabled"]; + disabled?: Props["isDisabled"]; errorMessage?: string; errorList?: string[]; - initialIsOpen?: SelectProps["defaultMenuIsOpen"]; - multiselect?: SelectProps["isMulti"]; + initialIsOpen?: Props["defaultMenuIsOpen"]; + multiselect?: Props["isMulti"]; maxHeight?: string; - size?: ComponentSize; - error?: boolean; - options: NDSOptionType[]; - onChange?: (newValue: unknown) => void; - [key: string]: any; -} + defaultValue?: PropsValue; + value?: PropsValue; + options: NDSOption[]; + onChange?: (newValue: PropsValue) => void; + windowThreshold?: number; +}; -export type NDSSelectProps> = Omit< - WindowedSelectProps, - "isSearchable" | "isDisabled" | "isMulti" | "defaultMenuIsOpen" | "defaultInputValue" | "options" | "onChange" +export type NDSSelectProps< + IsMulti extends boolean = boolean, + Group extends GroupBase = GroupBase +> = Omit< + Props, + keyof CustomProps | "isSearchable" | "isDisabled" | "defaultMenuIsOpen" | "isMulti" > & - CustomProps; - -export const SelectDefaultProps = { - autocomplete: true, - disabled: undefined, - defaultValue: undefined, - error: undefined, - errorMessage: undefined, - errorList: undefined, - labelText: undefined, - helpText: undefined, - noOptionsMessage: undefined, - requirementText: undefined, - id: undefined, - initialIsOpen: undefined, - maxHeight: "248px", - menuPosition: "absolute" as MenuPosition, - menuPlacement: "bottom" as MenuPlacement, - multiselect: false, - name: undefined, - onBlur: undefined, - onChange: undefined, - placeholder: undefined, - required: false, - value: undefined, - className: undefined, - classNamePrefix: "ndsSelect", // a prefix is required in react-select top put classes on all buttons to apply style overrides - menuIsOpen: undefined, - onMenuOpen: undefined, - onMenuClose: undefined, - onInputChange: undefined, - components: undefined, - closeMenuOnSelect: true, -}; + CustomProps; -const ReactSelect = React.forwardRef( - >( +const NDSSelect = forwardRef( + = GroupBase>( { - size, autocomplete, - options, + value, + onChange, + defaultValue, labelText, required, requirementText, @@ -111,74 +73,72 @@ const ReactSelect = React.forwardRef( disabled, errorMessage, errorList, - error = !!(errorMessage || errorList), id, initialIsOpen, maxHeight, multiselect, - onChange, placeholder, - value, - defaultValue, components, - "aria-label": ariaLabel, - windowThreshold = 300, + size, + windowThreshold, + options, ...props - }: NDSSelectProps, - ref + }: NDSSelectProps, + ref: + | ((instance: Select | null) => void) + | MutableRefObject | null> + | null ) => { const { t } = useTranslation(); - const themeContext = React.useContext(ThemeContext); + const theme = useTheme(); const spaceProps = getSubset(props, propTypes.space); - const reactSelectRef = React.useRef(null); + const error = !!(errorMessage || errorList); const optionsRef = React.useRef(options); - const componentSize = useComponentSize(size); + const optionsLength = React.useMemo(() => calcOptionsLength(options), [options]); + const isWindowed = optionsLength >= windowThreshold; React.useEffect(() => { checkOptionsAreValid(options); optionsRef.current = options; }, [options]); - React.useEffect(() => { - if (ref) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - ref.current = reactSelectRef.current; - } - }, [reactSelectRef, ref]); - return ( - windowThreshold, - })} - isDisabled={disabled} + { + isMulti={multiselect} + defaultValue={getReactSelectValue(options, defaultValue)} + value={getReactSelectValue(options, value)} + options={options} + onChange={(newValue) => { if (!onChange) return; - const value = extractValue(option as NDSOptionType | NDSOptionType[], multiselect); + const value = extractValue(newValue, multiselect); onChange(value); }} - defaultValue={getReactSelectValue(options, defaultValue)} - value={getReactSelectValue(options, value)} - isMulti={multiselect} + placeholder={placeholder || t("select")} + aria-required={required} + required={required} + aria-invalid={error} + inputId={id} + styles={customStyles({ + theme: theme, + error, + maxHeight, + size: componentSize, + windowed: options.length > windowThreshold, + })} components={{ - Option: (props) => , + Option: (props) => ( + + {props.children} + + ), Control: SelectControl, MultiValue: SelectMultiValue, ClearIndicator: SelectClearIndicator, @@ -186,10 +146,9 @@ const ReactSelect = React.forwardRef( SelectContainer: SelectContainer, Menu: SelectMenu, Input: SelectInput, + ...(isWindowed ? { MenuList } : {}), ...components, }} - aria-label={ariaLabel} - options={options} {...props} /> @@ -199,54 +158,4 @@ const ReactSelect = React.forwardRef( } ); -const checkOptionsAreValid = (options: NDSOptionType[]) => { - if (options && process.env.NODE_ENV === "development") { - const uniq = (a: unknown[]) => Array.from(new Set(a)); - - const uniqueValues = uniq(options.map(({ value }) => (value === null ? "_null_" : value))); - - if (uniqueValues.length < options.length) { - console.warn("NDS: The options prop passed to Select must have unique values for each option", options); - } - } -}; - -export const getOption = (options: NDSOptionType[], value: unknown) => { - // allows an option with a null value to be matched - if (options.length > 0 && value !== undefined) { - const optionWithMatchingValue = options.find((o) => o.value === value); - return optionWithMatchingValue || null; - } - return value; -}; - -const getReactSelectValue = (options: NDSOptionType[], input: unknown) => { - if (Array.isArray(input)) { - return input.map((i) => getOption(options, i)); - } - return getOption(options, input); -}; - -function extractValue(options: NDSOptionType[] | NDSOptionType, isMulti: boolean) { - if (Array.isArray(options)) { - if (isMulti) { - return options && options.length ? options.map((o) => o.value) : []; - } else { - throw new Error("UNEXPECTED ERROR: don't forget to enable isMulti"); - } - } - - if (options === null) { - return options; - } else { - return options.value; - } -} - -ReactSelect.defaultProps = { - ...SelectDefaultProps, - windowThreshold: 300, - filterOption: undefined, -}; - -export default ReactSelect; +export default NDSSelect; diff --git a/src/Select/SelectComponents.tsx b/src/Select/SelectComponents.tsx index 53b070750..58da9e1f4 100644 --- a/src/Select/SelectComponents.tsx +++ b/src/Select/SelectComponents.tsx @@ -1,64 +1,100 @@ import React from "react"; -import { components as selectComponents } from "react-windowed-select"; +import { + ClearIndicatorProps, + ContainerProps, + ControlProps, + DropdownIndicatorProps, + InputProps, + MenuProps, + MultiValueProps, + GroupBase, + components, +} from "react-select"; +import { NDSOption } from "./Select"; -export const SelectControl = (props) => { - // eslint-disable-next-line react/prop-types +export function SelectControl< + Option = NDSOption, + IsMulti extends boolean = boolean, + Group extends GroupBase