From 6afe23bd17cdccc27f6e4a4a13abafa3573a1941 Mon Sep 17 00:00:00 2001 From: thatmattlove Date: Mon, 6 Dec 2021 10:53:15 -0700 Subject: [PATCH] Upgrade react-select & improve select typing --- .../ui/components/form/queryLocation.tsx | 24 +- hyperglass/ui/components/form/queryTarget.tsx | 28 +-- hyperglass/ui/components/form/queryType.tsx | 46 ++-- hyperglass/ui/components/lookingGlass.tsx | 1 + hyperglass/ui/components/select/index.ts | 3 +- hyperglass/ui/components/select/option.tsx | 15 +- hyperglass/ui/components/select/select.tsx | 124 +++++----- hyperglass/ui/components/select/styles.ts | 219 ++++++++++++++++++ hyperglass/ui/components/select/styles.tsx | 192 --------------- hyperglass/ui/components/select/types.ts | 98 +++----- hyperglass/ui/hooks/useFormState.ts | 30 ++- hyperglass/ui/hooks/useOpposingColor.ts | 48 +++- hyperglass/ui/nextdev.js | 2 +- hyperglass/ui/package.json | 9 +- hyperglass/ui/types/common.ts | 19 +- hyperglass/ui/types/globals.d.ts | 8 + hyperglass/ui/yarn.lock | 117 +++++++--- 17 files changed, 554 insertions(+), 429 deletions(-) create mode 100644 hyperglass/ui/components/select/styles.ts delete mode 100644 hyperglass/ui/components/select/styles.tsx diff --git a/hyperglass/ui/components/form/queryLocation.tsx b/hyperglass/ui/components/form/queryLocation.tsx index b43046d..9e90360 100644 --- a/hyperglass/ui/components/form/queryLocation.tsx +++ b/hyperglass/ui/components/form/queryLocation.tsx @@ -5,11 +5,16 @@ import { useFormContext } from 'react-hook-form'; import { Select } from '~/components'; import { useConfig, useColorValue } from '~/context'; import { useOpposingColor, useFormState } from '~/hooks'; +import { isMultiValue, isSingleValue } from '~/components/select'; import type { DeviceGroup, SingleOption, OptionGroup, FormData } from '~/types'; +import type { SelectOnChange } from '~/components/select'; import type { TQuerySelectField, LocationCardProps } from './types'; -function buildOptions(devices: DeviceGroup[]): OptionGroup[] { +/** Location option type alias for future extensions. */ +type LocationOption = SingleOption; + +function buildOptions(devices: DeviceGroup[]): OptionGroup[] { return devices .map(group => { const label = group.group; @@ -39,7 +44,7 @@ const LocationCard = (props: LocationCardProps): JSX.Element => { const { label } = option; const [isChecked, setChecked] = useState(defaultChecked); - function handleChange(value: SingleOption) { + function handleChange(value: LocationOption) { if (isChecked) { setChecked(false); onChange('remove', value); @@ -176,15 +181,15 @@ export const QueryLocation = (props: TQuerySelectField): JSX.Element => { * @param options Final value. React-select determines if an option is being added or removed and * only sends back the final value. */ - function handleSelectChange(options: SingleOption[] | SingleOption): void { - if (Array.isArray(options)) { + const handleSelectChange: SelectOnChange = (options): void => { + if (isMultiValue(options)) { onChange({ field: 'queryLocation', value: options.map(o => o.value) }); - setSelection('queryLocation', options); - } else { + setSelection('queryLocation', options); + } else if (isSingleValue(options)) { onChange({ field: 'queryLocation', value: options.value }); - setSelection('queryLocation', [options]); + setSelection('queryLocation', [options]); } - } + }; if (element === 'cards') { return ( @@ -211,9 +216,8 @@ export const QueryLocation = (props: TQuerySelectField): JSX.Element => { ); } else if (element === 'select') { return ( - - name="queryType" options={options} aria-label={label} diff --git a/hyperglass/ui/components/lookingGlass.tsx b/hyperglass/ui/components/lookingGlass.tsx index 6047ec5..1a830be 100644 --- a/hyperglass/ui/components/lookingGlass.tsx +++ b/hyperglass/ui/components/lookingGlass.tsx @@ -160,6 +160,7 @@ export const LookingGlass = (): JSX.Element => { } else if (e.field === 'queryTarget' && isString(e.value)) { setFormValue('queryTarget', e.value); } + console.table(form); } useEffect(() => { diff --git a/hyperglass/ui/components/select/index.ts b/hyperglass/ui/components/select/index.ts index cbd13cf..242fc65 100644 --- a/hyperglass/ui/components/select/index.ts +++ b/hyperglass/ui/components/select/index.ts @@ -1,2 +1,3 @@ export * from './select'; -export type { TOptions } from './types'; +export { isSingleValue, isMultiValue } from './types'; +export type { SelectOnChange } from './types'; diff --git a/hyperglass/ui/components/select/option.tsx b/hyperglass/ui/components/select/option.tsx index a733ac8..4c26e9e 100644 --- a/hyperglass/ui/components/select/option.tsx +++ b/hyperglass/ui/components/select/option.tsx @@ -1,16 +1,17 @@ -import { Badge, Box, HStack } from '@chakra-ui/react'; +import { Badge, chakra, HStack } from '@chakra-ui/react'; import { components } from 'react-select'; -import type { TOption } from './types'; +import type { OptionProps, GroupBase } from 'react-select'; +import type { SingleOption } from '~/types'; -export const Option = (props: TOption): JSX.Element => { +export const Option = ( + props: OptionProps, +): JSX.Element => { const { label, data } = props; const tags = Array.isArray(data.tags) ? (data.tags as string[]) : []; return ( - - - {label} - + > {...props}> + {label} {tags.length > 0 && ( {tags.map(tag => ( diff --git a/hyperglass/ui/components/select/select.tsx b/hyperglass/ui/components/select/select.tsx index 0557db2..0f4d370 100644 --- a/hyperglass/ui/components/select/select.tsx +++ b/hyperglass/ui/components/select/select.tsx @@ -1,6 +1,6 @@ -import { createContext, useContext, useMemo } from 'react'; +import { createContext, forwardRef, useContext } from 'react'; import ReactSelect from 'react-select'; -import { chakra, useDisclosure } from '@chakra-ui/react'; +import { useDisclosure } from '@chakra-ui/react'; import { useColorMode } from '~/context'; import { Option } from './option'; import { @@ -17,67 +17,81 @@ import { useMultiValueRemoveStyle, useIndicatorSeparatorStyle, } from './styles'; +import { isSingleValue } from './types'; +import type { + Props as ReactSelectProps, + MultiValue, + OnChangeValue, + SelectInstance, +} from 'react-select'; import type { SingleOption } from '~/types'; -import type { TSelectBase, TSelectContext, TReactSelectChakra } from './types'; +import type { TSelectBase, TSelectContext } from './types'; const SelectContext = createContext({} as TSelectContext); export const useSelectContext = (): TSelectContext => useContext(SelectContext); -const ReactSelectChakra = chakra(ReactSelect); +export const Select = forwardRef( + ( + props: TSelectBase, + ref: React.Ref>, + ): JSX.Element => { + const { options, isMulti, onSelect, isError = false, components, ...rest } = props; -export const Select: React.FC = (props: TSelectBase) => { - const { options, multi, onSelect, isError = false, components, ...rest } = props; - const { isOpen, onOpen, onClose } = useDisclosure(); + const { isOpen, onOpen, onClose } = useDisclosure(); - const { colorMode } = useColorMode(); + const { colorMode } = useColorMode(); - const selectContext = useMemo( - () => ({ colorMode, isOpen, isError }), - [colorMode, isError, isOpen], - ); + const defaultOnChange: ReactSelectProps['onChange'] = changed => { + if (isSingleValue(changed)) { + changed = [changed] as unknown as OnChangeValue; + } + if (typeof onSelect === 'function') { + onSelect(changed as MultiValue); + } + }; - const defaultOnChange = (changed: SingleOption | SingleOption[]) => { - if (!Array.isArray(changed)) { - changed = [changed]; - } - if (typeof onSelect === 'function') { - onSelect(changed); - } - }; + const menu = useMenuStyle({ colorMode }); + const menuList = useMenuListStyle({ colorMode }); + const control = useControlStyle({ colorMode }); + const option = useOptionStyle({ colorMode }); + const singleValue = useSingleValueStyle({ colorMode }); + const multiValue = useMultiValueStyle({ colorMode }); + const multiValueLabel = useMultiValueLabelStyle({ colorMode }); + const multiValueRemove = useMultiValueRemoveStyle({ colorMode }); + const menuPortal = useMenuPortal(); + const placeholder = usePlaceholderStyle({ colorMode }); + const indicatorSeparator = useIndicatorSeparatorStyle({ colorMode }); + const rsTheme = useRSTheme(); - const multiValue = useMultiValueStyle({ colorMode }); - const multiValueLabel = useMultiValueLabelStyle({ colorMode }); - const multiValueRemove = useMultiValueRemoveStyle({ colorMode }); - const menuPortal = useMenuPortal(); - const rsTheme = useRSTheme(); - - return ( - - - - ); -}; + return ( + + + onChange={defaultOnChange} + onMenuClose={onClose} + onMenuOpen={onOpen} + isClearable={true} + options={options} + isMulti={isMulti} + theme={rsTheme} + components={{ Option, ...components }} + ref={ref} + styles={{ + menu, + option, + control, + menuList, + menuPortal, + multiValue, + singleValue, + placeholder, + multiValueLabel, + multiValueRemove, + indicatorSeparator, + }} + {...rest} + /> + + ); + }, +); diff --git a/hyperglass/ui/components/select/styles.ts b/hyperglass/ui/components/select/styles.ts new file mode 100644 index 0000000..2b5a0dd --- /dev/null +++ b/hyperglass/ui/components/select/styles.ts @@ -0,0 +1,219 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +import { useCallback } from 'react'; +import { useToken } from '@chakra-ui/react'; +import { mergeWith } from '@chakra-ui/utils'; +import { merge } from 'merge-anything'; +import { useOpposingColor, useOpposingColorCallback } from '~/hooks'; +import { useColorValue, useColorToken, useMobile } from '~/context'; +import { useSelectContext } from './select'; + +import * as ReactSelect from 'react-select'; +import type { SingleOption } from '~/types'; +import type { RSStyleCallbackProps, RSThemeFunction, RSStyleFunction } from './types'; + +export const useControlStyle = ( + props: RSStyleCallbackProps, +): RSStyleFunction<'control', Opt, IsMulti> => { + const { colorMode } = props; + const { isError } = useSelectContext(); + + const minHeight = useToken('space', 12); + const borderRadius = useToken('radii', 'md'); + const color = useColorToken('colors', 'black', 'whiteAlpha.800'); + const focusBorder = useColorToken('colors', 'blue.500', 'blue.300'); + const invalidBorder = useColorToken('colors', 'red.500', 'red.300'); + const borderColor = useColorToken('colors', 'gray.100', 'whiteAlpha.50'); + const borderHover = useColorToken('colors', 'gray.300', 'whiteAlpha.400'); + const backgroundColor = useColorToken('colors', 'white', 'whiteAlpha.100'); + + return useCallback( + (base, state) => { + const { isFocused } = state; + const styles = { + backgroundColor, + borderRadius, + color, + minHeight, + transition: 'all 0.2s', + borderColor: isError ? invalidBorder : isFocused ? focusBorder : borderColor, + boxShadow: isError + ? `0 0 0 1px ${invalidBorder}` + : isFocused + ? `0 0 0 1px ${focusBorder}` + : undefined, + '&:hover': { borderColor: isFocused ? focusBorder : borderHover }, + '&:hover > div > span': { backgroundColor: borderHover }, + '&:focus': { borderColor: isError ? invalidBorder : focusBorder }, + '&.invalid': { borderColor: invalidBorder, boxShadow: `0 0 0 1px ${invalidBorder}` }, + }; + return mergeWith({}, base, styles); + }, + [colorMode, isError], + ); +}; + +export const useMenuStyle = ( + props: RSStyleCallbackProps, +): RSStyleFunction<'menu', Opt, IsMulti> => { + const { colorMode } = props; + const { isOpen } = useSelectContext(); + const backgroundColor = useColorToken('colors', 'white', 'blackSolid.700'); + const styles = { backgroundColor, zIndex: 1500 }; + return useCallback(base => mergeWith({}, base, styles), [colorMode, isOpen]); +}; + +export const useMenuListStyle = ( + props: RSStyleCallbackProps, +): RSStyleFunction<'menuList', Opt, IsMulti> => { + const { colorMode } = props; + const { isOpen } = useSelectContext(); + const borderRadius = useToken('radii', 'md'); + const backgroundColor = useColorToken('colors', 'white', 'blackSolid.700'); + const scrollbarTrack = useColorToken('colors', 'blackAlpha.50', 'whiteAlpha.50'); + const scrollbarThumb = useColorToken('colors', 'blackAlpha.300', 'whiteAlpha.300'); + const scrollbarThumbHover = useColorToken('colors', 'blackAlpha.400', 'whiteAlpha.400'); + + const styles = { + borderRadius, + backgroundColor, + '&::-webkit-scrollbar': { width: '5px' }, + '&::-webkit-scrollbar-track': { backgroundColor: scrollbarTrack }, + '&::-webkit-scrollbar-thumb': { backgroundColor: scrollbarThumb }, + '&::-webkit-scrollbar-thumb:hover': { backgroundColor: scrollbarThumbHover }, + '-ms-overflow-style': { display: 'none' }, + }; + return useCallback(base => mergeWith({}, base, styles), [colorMode, isOpen]); +}; + +export const useOptionStyle = ( + props: RSStyleCallbackProps, +): RSStyleFunction<'option', Opt, IsMulti> => { + const { colorMode } = props; + const { isOpen } = useSelectContext(); + + const fontSize = useToken('fontSizes', 'lg'); + const disabled = useToken('colors', 'whiteAlpha.400'); + const active = useColorToken('colors', 'primary.600', 'primary.400'); + const focused = useColorToken('colors', 'primary.500', 'primary.300'); + const selected = useColorToken('colors', 'blackAlpha.400', 'whiteAlpha.400'); + + const activeColor = useOpposingColor(active); + const getColor = useOpposingColorCallback(); + + return useCallback( + (base, state) => { + const { isFocused, isSelected, isDisabled } = state; + + let backgroundColor = 'transparent'; + switch (true) { + case isDisabled: + backgroundColor = disabled; + break; + case isSelected: + backgroundColor = selected; + break; + case isFocused: + backgroundColor = focused; + break; + } + const color = getColor(backgroundColor); + + const styles = { + color: backgroundColor === 'transparent' ? 'currentColor' : color, + '&:active': { backgroundColor: active, color: activeColor }, + '&:focus': { backgroundColor: active, color: activeColor }, + backgroundColor, + fontSize, + }; + + return mergeWith({}, base, styles); + }, + [isOpen, colorMode], + ); +}; + +export const useIndicatorSeparatorStyle = ( + props: RSStyleCallbackProps, +): RSStyleFunction<'indicatorSeparator', Opt, IsMulti> => { + const { colorMode } = props; + const backgroundColor = useColorToken('colors', 'whiteAlpha.700', 'gray.600'); + const styles = { backgroundColor }; + return useCallback(base => mergeWith({}, base, styles), [colorMode]); +}; + +export const usePlaceholderStyle = ( + props: RSStyleCallbackProps, +): RSStyleFunction<'placeholder', Opt, IsMulti> => { + const { colorMode } = props; + const color = useColorToken('colors', 'gray.600', 'whiteAlpha.700'); + const fontSize = useToken('fontSizes', 'lg'); + return useCallback(base => mergeWith({}, base, { color, fontSize }), [colorMode]); +}; + +export const useSingleValueStyle = ( + props: RSStyleCallbackProps, +): RSStyleFunction<'singleValue', Opt, IsMulti> => { + const { colorMode } = props; + + const color = useColorValue('black', 'whiteAlpha.800'); + const fontSize = useToken('fontSizes', 'lg'); + + const styles = { color, fontSize }; + return useCallback(base => mergeWith({}, base, styles), [color, colorMode]); +}; + +export const useMultiValueStyle = ( + props: RSStyleCallbackProps, +): RSStyleFunction<'multiValue', Opt, IsMulti> => { + const { colorMode } = props; + + const backgroundColor = useColorToken('colors', 'primary.500', 'primary.300'); + const color = useOpposingColor(backgroundColor); + + const styles = { backgroundColor, color }; + return useCallback(base => mergeWith({}, base, styles), [backgroundColor, colorMode]); +}; + +export const useMultiValueLabelStyle = ( + props: RSStyleCallbackProps, +): RSStyleFunction<'multiValueLabel', Opt, IsMulti> => { + const { colorMode } = props; + + const backgroundColor = useColorToken('colors', 'primary.500', 'primary.300'); + const color = useOpposingColor(backgroundColor); + + const styles = { color }; + return useCallback(base => mergeWith({}, base, styles), [colorMode]); +}; + +export const useMultiValueRemoveStyle = ( + props: RSStyleCallbackProps, +): RSStyleFunction<'multiValueRemove', Opt, IsMulti> => { + const { colorMode } = props; + + const backgroundColor = useColorToken('colors', 'primary.500', 'primary.300'); + const color = useOpposingColor(backgroundColor); + + const styles = { + color, + '&:hover': { backgroundColor: 'inherit', color, opacity: 0.7 }, + }; + return useCallback(base => mergeWith({}, base, styles), [colorMode]); +}; + +export const useRSTheme = (): RSThemeFunction => { + const borderRadius = useToken('radii', 'md'); + return useCallback((t: ReactSelect.Theme): ReactSelect.Theme => ({ ...t, borderRadius }), []); +}; + +export const useMenuPortal = (): RSStyleFunction< + 'menuPortal', + Opt, + IsMulti +> => { + const isMobile = useMobile(); + const styles = { + zIndex: isMobile ? 1500 : 1, + }; + return useCallback(base => merge(base, styles), [isMobile]); +}; diff --git a/hyperglass/ui/components/select/styles.tsx b/hyperglass/ui/components/select/styles.tsx deleted file mode 100644 index 678e264..0000000 --- a/hyperglass/ui/components/select/styles.tsx +++ /dev/null @@ -1,192 +0,0 @@ -/* eslint-disable react-hooks/exhaustive-deps */ -import { useCallback, useMemo } from 'react'; -import { useToken } from '@chakra-ui/react'; -import { mergeWith } from '@chakra-ui/utils'; -import { useOpposingColor } from '~/hooks'; -import { useColorValue, useColorToken, useMobile } from '~/context'; -import { useSelectContext } from './select'; - -import type { - TMenu, - TOption, - TStyles, - TControl, - TRSTheme, - TMultiValue, - TRSThemeCallback, - TRSStyleCallback, -} from './types'; - -export const useControlStyle = (base: TStyles, state: TControl): TStyles => { - const { isFocused } = state; - const { colorMode, isError } = useSelectContext(); - - const minHeight = useToken('space', 12); - const borderRadius = useToken('radii', 'md'); - const color = useColorToken('colors', 'black', 'whiteAlpha.800'); - const focusBorder = useColorToken('colors', 'blue.500', 'blue.300'); - const invalidBorder = useColorToken('colors', 'red.500', 'red.300'); - const borderColor = useColorToken('colors', 'gray.100', 'whiteAlpha.50'); - const borderHover = useColorToken('colors', 'gray.300', 'whiteAlpha.400'); - const backgroundColor = useColorToken('colors', 'white', 'whiteAlpha.100'); - - const styles = { - backgroundColor, - borderRadius, - color, - minHeight, - transition: 'all 0.2s', - borderColor: isError ? invalidBorder : isFocused ? focusBorder : borderColor, - boxShadow: isError - ? `0 0 0 1px ${invalidBorder}` - : isFocused - ? `0 0 0 1px ${focusBorder}` - : undefined, - '&:hover': { borderColor: isFocused ? focusBorder : borderHover }, - '&:hover > div > span': { backgroundColor: borderHover }, - '&:focus': { borderColor: isError ? invalidBorder : focusBorder }, - '&.invalid': { borderColor: invalidBorder, boxShadow: `0 0 0 1px ${invalidBorder}` }, - }; - return useMemo(() => mergeWith({}, base, styles), [colorMode, isFocused, isError]); -}; - -export const useMenuStyle = (base: TStyles, _: TMenu): TStyles => { - const { colorMode, isOpen } = useSelectContext(); - const backgroundColor = useColorToken('colors', 'white', 'blackSolid.700'); - const styles = { backgroundColor }; - return useMemo(() => mergeWith({}, base, styles), [colorMode, isOpen]); -}; - -export const useMenuListStyle = (base: TStyles): TStyles => { - const { colorMode, isOpen } = useSelectContext(); - const borderRadius = useToken('radii', 'md'); - const backgroundColor = useColorToken('colors', 'white', 'blackSolid.700'); - const scrollbarTrack = useColorToken('colors', 'blackAlpha.50', 'whiteAlpha.50'); - const scrollbarThumb = useColorToken('colors', 'blackAlpha.300', 'whiteAlpha.300'); - const scrollbarThumbHover = useColorToken('colors', 'blackAlpha.400', 'whiteAlpha.400'); - - const styles = { - borderRadius, - backgroundColor, - '&::-webkit-scrollbar': { width: '5px' }, - '&::-webkit-scrollbar-track': { backgroundColor: scrollbarTrack }, - '&::-webkit-scrollbar-thumb': { backgroundColor: scrollbarThumb }, - '&::-webkit-scrollbar-thumb:hover': { backgroundColor: scrollbarThumbHover }, - '-ms-overflow-style': { display: 'none' }, - }; - return useMemo(() => mergeWith({}, base, styles), [colorMode, isOpen]); -}; - -export const useOptionStyle = (base: TStyles, state: TOption): TStyles => { - const { isFocused, isSelected, isDisabled } = state; - const { colorMode, isOpen } = useSelectContext(); - - const fontSize = useToken('fontSizes', 'lg'); - const disabled = useToken('colors', 'whiteAlpha.400'); - const active = useColorToken('colors', 'primary.600', 'primary.400'); - const focused = useColorToken('colors', 'primary.500', 'primary.300'); - const selected = useColorToken('colors', 'blackAlpha.400', 'whiteAlpha.400'); - - const activeColor = useOpposingColor(active); - - const backgroundColor = useMemo(() => { - let bg = 'transparent'; - switch (true) { - case isDisabled: - bg = disabled; - break; - case isSelected: - bg = selected; - break; - case isFocused: - bg = focused; - break; - } - return bg; - }, [isDisabled, isFocused, isSelected]); - - const color = useOpposingColor(backgroundColor); - - const styles = { - color: backgroundColor === 'transparent' ? 'currentColor' : color, - '&:active': { backgroundColor: active, color: activeColor }, - '&:focus': { backgroundColor: active, color: activeColor }, - backgroundColor, - fontSize, - }; - - return useMemo( - () => mergeWith({}, base, styles), - [isOpen, colorMode, isFocused, isDisabled, isSelected], - ); -}; - -export const useIndicatorSeparatorStyle = (base: TStyles): TStyles => { - const { colorMode } = useSelectContext(); - const backgroundColor = useColorToken('colors', 'whiteAlpha.700', 'gray.600'); - const styles = { backgroundColor }; - return useMemo(() => mergeWith({}, base, styles), [colorMode]); -}; - -export const usePlaceholderStyle = (base: TStyles): TStyles => { - const { colorMode } = useSelectContext(); - const color = useColorToken('colors', 'gray.600', 'whiteAlpha.700'); - const fontSize = useToken('fontSizes', 'lg'); - return useMemo(() => mergeWith({}, base, { color, fontSize }), [colorMode]); -}; - -export const useSingleValueStyle = (): TRSStyleCallback => { - const { colorMode } = useSelectContext(); - - const color = useColorValue('black', 'whiteAlpha.800'); - const fontSize = useToken('fontSizes', 'lg'); - - const styles = { color, fontSize }; - return useCallback((base: TStyles) => mergeWith({}, base, styles), [color, colorMode]); -}; - -export const useMultiValueStyle = (props: TMultiValue): TRSStyleCallback => { - const { colorMode } = props; - - const backgroundColor = useColorToken('colors', 'primary.500', 'primary.300'); - const color = useOpposingColor(backgroundColor); - - const styles = { backgroundColor, color }; - return useCallback((base: TStyles) => mergeWith({}, base, styles), [backgroundColor, colorMode]); -}; - -export const useMultiValueLabelStyle = (props: TMultiValue): TRSStyleCallback => { - const { colorMode } = props; - - const backgroundColor = useColorToken('colors', 'primary.500', 'primary.300'); - const color = useOpposingColor(backgroundColor); - - const styles = { color }; - return useCallback((base: TStyles) => mergeWith({}, base, styles), [colorMode]); -}; - -export const useMultiValueRemoveStyle = (props: TMultiValue): TRSStyleCallback => { - const { colorMode } = props; - - const backgroundColor = useColorToken('colors', 'primary.500', 'primary.300'); - const color = useOpposingColor(backgroundColor); - - const styles = { - color, - '&:hover': { backgroundColor: 'inherit', color, opacity: 0.7 }, - }; - return useCallback((base: TStyles) => mergeWith({}, base, styles), [colorMode]); -}; - -export const useRSTheme = (): TRSThemeCallback => { - const borderRadius = useToken('radii', 'md'); - return useCallback((t: TRSTheme): TRSTheme => ({ ...t, borderRadius }), []); -}; - -export const useMenuPortal = (): TRSStyleCallback => { - const isMobile = useMobile(); - const styles = { - zIndex: isMobile ? 1500 : 1, - }; - return useCallback((base: TStyles) => mergeWith({}, base, styles), [isMobile]); -}; diff --git a/hyperglass/ui/components/select/types.ts b/hyperglass/ui/components/select/types.ts index aca4828..b464827 100644 --- a/hyperglass/ui/components/select/types.ts +++ b/hyperglass/ui/components/select/types.ts @@ -1,38 +1,20 @@ -/* eslint @typescript-eslint/no-explicit-any: 0 */ -/* eslint @typescript-eslint/explicit-module-boundary-types: 0 */ +import * as ReactSelect from 'react-select'; -import type { - Props as IReactSelect, - ControlProps, - MenuProps, - MenuListComponentProps, - OptionProps, - MultiValueProps, - IndicatorProps, - Theme as RSTheme, - PlaceholderProps, - Styles as RSStyles, -} from 'react-select'; -import type { BoxProps } from '@chakra-ui/react'; -import type { Theme, SingleOption, OptionGroup } from '~/types'; +import type { StylesProps, StylesConfigFunction } from 'react-select/dist/declarations/src/styles'; +import type { Theme, SingleOption } from '~/types'; -export interface TSelectState { - [k: string]: string[]; -} +export type SelectOnChange< + Opt extends SingleOption = SingleOption, + IsMulti extends boolean = boolean, +> = NonNullable['onChange']>; -export type TOptions = Array; - -export type TReactSelectChakra = Omit & - Omit; - -export interface TSelectBase extends TReactSelectChakra { +export interface TSelectBase + extends ReactSelect.Props { name: string; - multi?: boolean; + isMulti?: IsMulti; isError?: boolean; - options: TOptions; required?: boolean; - onSelect?: (s: SingleOption[]) => void; - onChange?: (c: SingleOption | SingleOption[]) => void; + onSelect?: (s: ReactSelect.MultiValue) => void; colorScheme?: Theme.ColorNames; } @@ -42,40 +24,32 @@ export interface TSelectContext { isError: boolean; } -export interface TMultiValueRemoveProps { - children: Node; - data: any; - innerProps: { - className: string; - onTouchEnd: (e: any) => void; - onClick: (e: any) => void; - onMouseDown: (e: any) => void; - }; - selectProps: any; +export interface RSStyleCallbackProps { + colorMode: 'light' | 'dark'; } -export interface TRSTheme extends Omit { - borderRadius: string | number; +type StyleConfigKeys = keyof ReactSelect.StylesConfig< + SingleOption, + boolean, + ReactSelect.GroupBase +>; + +export type RSStyleFunction< + K extends StyleConfigKeys, + Opt extends SingleOption, + IsMulti extends boolean, +> = StylesConfigFunction>[K]>; + +export type RSThemeFunction = (theme: ReactSelect.Theme) => ReactSelect.Theme; + +export function isSingleValue( + value: ReactSelect.SingleValue | ReactSelect.MultiValue, +): value is NonNullable> { + return value !== null && !Array.isArray(value); } -export type TControl = ControlProps; - -export type TMenu = MenuProps; - -export type TMenuList = MenuListComponentProps; - -export type TOption = OptionProps; - -export type TMultiValueState = MultiValueProps; - -export type TIndicator = IndicatorProps; - -export type TPlaceholder = PlaceholderProps; - -export type TMultiValue = Pick; - -export type TRSStyleCallback = (base: TStyles) => TStyles; - -export type TRSThemeCallback = (theme: TRSTheme) => TRSTheme; - -export type TStyles = RSStyles; +export function isMultiValue( + value: ReactSelect.SingleValue | ReactSelect.MultiValue, +): value is NonNullable> { + return value !== null && Array.isArray(value); +} diff --git a/hyperglass/ui/hooks/useFormState.ts b/hyperglass/ui/hooks/useFormState.ts index d1fe941..c163f62 100644 --- a/hyperglass/ui/hooks/useFormState.ts +++ b/hyperglass/ui/hooks/useFormState.ts @@ -5,8 +5,9 @@ import plur from 'plur'; import isEqual from 'react-fast-compare'; import { all, andJoin, dedupObjectArray, withDev } from '~/util'; +import type { SingleValue, MultiValue } from 'react-select'; import type { StateCreator } from 'zustand'; -import type { UseFormSetError, UseFormClearErrors } from 'react-hook-form'; +import { UseFormSetError, UseFormClearErrors } from 'react-hook-form'; import type { SingleOption, Directive, FormData, Text } from '~/types'; import type { UseDevice } from './types'; @@ -21,9 +22,9 @@ interface FormValues { /** * Selected *options*, vs. values. */ -interface FormSelections { - queryLocation: SingleOption[]; - queryType: SingleOption | null; +interface FormSelections { + queryLocation: MultiValue; + queryType: SingleValue; } interface Filtered { @@ -39,13 +40,13 @@ interface Target { display: string; } -interface FormStateType { +interface FormStateType { // Values filtered: Filtered; form: FormValues; loading: boolean; responses: Responses; - selections: FormSelections; + selections: FormSelections; status: FormStatus; target: Target; resolvedIsOpen: boolean; @@ -57,7 +58,13 @@ interface FormStateType { addResponse(deviceId: string, data: QueryResponse): void; setLoading(value: boolean): void; setStatus(value: FormStatus): void; - setSelection(field: K, value: FormSelections[K]): void; + setSelection< + Opt extends SingleOption, + K extends keyof FormSelections = keyof FormSelections, + >( + field: K, + value: FormSelections[K], + ): void; setTarget(update: Partial): void; getDirective(): Directive | null; reset(): void; @@ -95,7 +102,10 @@ const formState: StateCreator = (set, get) => ({ set({ status }); }, - setSelection(field: K, value: FormSelections[K]): void { + setSelection< + Opt extends SingleOption, + K extends keyof FormSelections = keyof FormSelections, + >(field: K, value: FormSelections[K]): void { set(state => ({ selections: { ...state.selections, [field]: value } })); }, @@ -223,6 +233,10 @@ export const useFormState = create( withDev(formState, 'useFormState'), ); +export function useFormSelections(): FormSelections { + return useFormState(s => s.selections as FormSelections); +} + export function useView(): FormStatus { const { status, form } = useFormState(({ status, form }) => ({ status, form })); return useMemo(() => { diff --git a/hyperglass/ui/hooks/useOpposingColor.ts b/hyperglass/ui/hooks/useOpposingColor.ts index 52b29f0..129558b 100644 --- a/hyperglass/ui/hooks/useOpposingColor.ts +++ b/hyperglass/ui/hooks/useOpposingColor.ts @@ -1,25 +1,37 @@ -import { useMemo } from 'react'; +import { useMemo, useCallback } from 'react'; import { getColor, isLight } from '@chakra-ui/theme-tools'; import { useTheme } from '~/context'; import type { TOpposingOptions } from './types'; +export type UseIsDarkCallbackReturn = (color: string) => boolean; + /** * Parse the color string to determine if it's a Chakra UI theme key, and determine if the * opposing color should be black or white. */ export function useIsDark(color: string): boolean { + const isDarkFn = useIsDarkCallback(); + return useMemo((): boolean => isDarkFn(color), [color, isDarkFn]); +} + +export function useIsDarkCallback(): UseIsDarkCallbackReturn { const theme = useTheme(); - if (typeof color === 'string' && color.match(/[a-zA-Z]+\.[a-zA-Z0-9]+/g)) { - color = getColor(theme, color, color); - } - let opposingShouldBeDark = true; - try { - opposingShouldBeDark = isLight(color)(theme); - } catch (err) { - console.error(err); - } - return opposingShouldBeDark; + return useCallback( + (color: string): boolean => { + if (typeof color === 'string' && color.match(/[a-zA-Z]+\.[a-zA-Z0-9]+/g)) { + color = getColor(theme, color, color); + } + let opposingShouldBeDark = true; + try { + opposingShouldBeDark = isLight(color)(theme); + } catch (err) { + console.error(err); + } + return opposingShouldBeDark; + }, + [theme], + ); } /** @@ -36,3 +48,17 @@ export function useOpposingColor(color: string, options?: TOpposingOptions): str } }, [isBlack, options?.dark, options?.light]); } + +export function useOpposingColorCallback(options?: TOpposingOptions): (color: string) => string { + const isDark = useIsDarkCallback(); + return useCallback( + (color: string) => { + const isBlack = isDark(color); + if (isBlack) { + return options?.dark ?? 'black'; + } + return options?.light ?? 'white'; + }, + [isDark, options?.dark, options?.light], + ); +} diff --git a/hyperglass/ui/nextdev.js b/hyperglass/ui/nextdev.js index 4acc93f..6720320 100644 --- a/hyperglass/ui/nextdev.js +++ b/hyperglass/ui/nextdev.js @@ -33,7 +33,7 @@ app // Set up the proxy. if (dev && devProxy) { - Object.keys(devProxy).forEach(function (context) { + Object.keys(devProxy).forEach(context => { server.use(proxyMiddleware(context, devProxy[context])); }); } diff --git a/hyperglass/ui/package.json b/hyperglass/ui/package.json index fd4966f..c4619a5 100644 --- a/hyperglass/ui/package.json +++ b/hyperglass/ui/package.json @@ -7,11 +7,11 @@ "private": true, "scripts": { "lint": "eslint . --ext .ts --ext .tsx", - "dev": "node nextdev", + "dev": "export NODE_OPTIONS=--openssl-legacy-provider; node nextdev", "start": "next start", "typecheck": "tsc --noEmit", "format": "prettier --config ./.prettierrc -c -w .", - "build": "next build && next export -o ../hyperglass/static/ui", + "build": "export NODE_OPTIONS=--openssl-legacy-provider; next build && next export -o ../hyperglass/static/ui", "test": "jest" }, "browserslist": "> 0.25%, not dead", @@ -27,6 +27,7 @@ "dayjs": "^1.10.4", "framer-motion": "^4.1.17", "lodash": "^4.17.21", + "merge-anything": "^4.0.1", "next": "^11.1.2", "palette-by-numbers": "^0.1.5", "plur": "^4.0.0", @@ -40,7 +41,7 @@ "react-hook-form": "^7.7.0", "react-markdown": "^5.0.3", "react-query": "^3.16.0", - "react-select": "^4.3.1", + "react-select": "^5.2.1", "react-table": "^7.7.0", "remark-gfm": "^1.0.0", "string-format": "^2.0.0", @@ -51,9 +52,9 @@ "@testing-library/jest-dom": "^5.14.1", "@testing-library/react": "^12.1.0", "@types/dagre": "^0.7.44", + "@types/express": "^4.17.13", "@types/node": "^14.14.41", "@types/react": "^17.0.3", - "@types/react-select": "^4.0.15", "@types/react-table": "^7.7.1", "@types/string-format": "^2.0.0", "@typescript-eslint/eslint-plugin": "^4.31.0", diff --git a/hyperglass/ui/types/common.ts b/hyperglass/ui/types/common.ts index 098e56f..80d954f 100644 --- a/hyperglass/ui/types/common.ts +++ b/hyperglass/ui/types/common.ts @@ -1,18 +1,19 @@ -type AnyOption = { +interface AnyOption { label: string; -}; +} -export type SingleOption = AnyOption & { +export interface SingleOption = Record> + extends AnyOption { value: string; group?: string; tags?: string[]; - data?: Record; -}; + data?: T; +} -export type OptionGroup = AnyOption & { - options: SingleOption[]; -}; +export interface OptionGroup extends AnyOption { + options: Opt[]; +} -export type SelectOption = (SingleOption | OptionGroup) & { data: T }; +export type OptionsOrGroup = Array>; export type OnChangeArgs = { field: string; value: string | string[] }; diff --git a/hyperglass/ui/types/globals.d.ts b/hyperglass/ui/types/globals.d.ts index ba6d739..ab5a45a 100644 --- a/hyperglass/ui/types/globals.d.ts +++ b/hyperglass/ui/types/globals.d.ts @@ -61,3 +61,11 @@ declare global { } } } + +declare module 'react' { + // Enable generic typing with forwardRef. + // eslint-disable-next-line @typescript-eslint/ban-types + function forwardRef( + render: (props: P, ref: React.Ref) => React.ReactElement | null, + ): (props: P & React.RefAttributes) => React.ReactElement | null; +} diff --git a/hyperglass/ui/yarn.lock b/hyperglass/ui/yarn.lock index dbe052a..4748c5e 100644 --- a/hyperglass/ui/yarn.lock +++ b/hyperglass/ui/yarn.lock @@ -1666,6 +1666,21 @@ dependencies: "@babel/types" "^7.3.0" +"@types/body-parser@*": + version "1.19.2" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" + integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.35" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" + integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== + dependencies: + "@types/node" "*" + "@types/d3-array@*": version "2.9.0" resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-2.9.0.tgz#fb6c3d7d7640259e68771cd90cc5db5ac1a1a012" @@ -1891,6 +1906,25 @@ resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag== +"@types/express-serve-static-core@^4.17.18": + version "4.17.25" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.25.tgz#e42f7046adc65ece2eb6059b77aecfbe9e9f82e0" + integrity sha512-OUJIVfRMFijZukGGwTpKNFprqCCXk5WjNGvUgB/CxxBR40QWSjsNK86+yvGKlCOGc7sbwfHLaXhkG+NsytwBaQ== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + +"@types/express@^4.17.13": + version "4.17.13" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034" + integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.18" + "@types/qs" "*" + "@types/serve-static" "*" + "@types/geojson@*": version "7946.0.7" resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.7.tgz#c8fa532b60a0042219cdf173ca21a975ef0666ad" @@ -1972,6 +2006,11 @@ dependencies: "@types/unist" "*" +"@types/mime@^1": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" + integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== + "@types/node@*", "@types/node@^14.14.41": version "14.14.41" resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.41.tgz#d0b939d94c1d7bd53d04824af45f1139b8c45615" @@ -1992,12 +2031,15 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw== -"@types/react-dom@*": - version "16.9.8" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.8.tgz#fe4c1e11dfc67155733dfa6aa65108b4971cb423" - integrity sha512-ykkPQ+5nFknnlU6lDd947WbQ6TE3NNzbQAkInC2EKY1qeYdTKp7onFusmYZb+ityzx2YviqT6BXSu+LyWWJwcA== - dependencies: - "@types/react" "*" +"@types/qs@*": + version "6.9.7" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" + integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== + +"@types/range-parser@*": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" + integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== "@types/react-redux@^7.1.16": version "7.1.16" @@ -2009,16 +2051,6 @@ hoist-non-react-statics "^3.3.0" redux "^4.0.0" -"@types/react-select@^4.0.15": - version "4.0.15" - resolved "https://registry.yarnpkg.com/@types/react-select/-/react-select-4.0.15.tgz#2e6a1cff22c4bbae6c95b8dbee5b5097c12eae54" - integrity sha512-GPyBFYGMVFCtF4eg9riodEco+s2mflR10Nd5csx69+bcdvX6Uo9H/jgrIqovBU9yxBppB9DS66OwD6xxgVqOYQ== - dependencies: - "@emotion/serialize" "^1.0.0" - "@types/react" "*" - "@types/react-dom" "*" - "@types/react-transition-group" "*" - "@types/react-table@^7.7.1": version "7.7.1" resolved "https://registry.yarnpkg.com/@types/react-table/-/react-table-7.7.1.tgz#cac73133fc185e152e31435f8e6fce89ab868661" @@ -2026,10 +2058,10 @@ dependencies: "@types/react" "*" -"@types/react-transition-group@*": - version "4.4.0" - resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.0.tgz#882839db465df1320e4753e6e9f70ca7e9b4d46d" - integrity sha512-/QfLHGpu+2fQOqQaXh8MG9q03bFENooTb/it4jr5kKaZlDQfWvjqWZg48AwzPVMBHlRuTRAY7hRHCEOXz5kV6w== +"@types/react-transition-group@^4.4.0": + version "4.4.4" + resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.4.tgz#acd4cceaa2be6b757db61ed7b432e103242d163e" + integrity sha512-7gAPz7anVK5xzbeQW9wFBDg7G++aPLAFY0QaSMOou9rJZpbuI58WAuJrgu+qR92l61grlnCUe7AFX8KGahAgug== dependencies: "@types/react" "*" @@ -2055,6 +2087,14 @@ resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.1.tgz#18845205e86ff0038517aab7a18a62a6b9f71275" integrity sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA== +"@types/serve-static@*": + version "1.13.10" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.10.tgz#f5e0ce8797d2d7cc5ebeda48a52c96c4fa47a8d9" + integrity sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ== + dependencies: + "@types/mime" "^1" + "@types/node" "*" + "@types/stack-utils@^2.0.0": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" @@ -4982,6 +5022,11 @@ is-typedarray@^1.0.0: resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= +is-what@^3.14.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/is-what/-/is-what-3.14.1.tgz#e1222f46ddda85dead0fd1c9df131760e77755c1" + integrity sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA== + isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -5862,6 +5907,14 @@ memoize-one@^5.0.0: resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.1.1.tgz#047b6e3199b508eaec03504de71229b8eb1d75c0" integrity sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA== +merge-anything@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/merge-anything/-/merge-anything-4.0.1.tgz#5c837cfa7adbb65fa5a4df178b37312493cb3609" + integrity sha512-KsFjBYc3juDoHz9Vzd5fte1nqp06H8SQ+yU344Dd0ZunwSgtltnC0kgKds8cbocJGyViLcBQuHkitbDXAqW+LQ== + dependencies: + is-what "^3.14.1" + ts-toolbelt "^9.3.12" + merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" @@ -6704,7 +6757,7 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2: +prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -6906,13 +6959,6 @@ react-hook-form@^7.7.0: resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.7.0.tgz#11072091fde39775ad834321d9f18f160d47e997" integrity sha512-WhTl6lbQrV942yzmDL+Eq9AGwG0gARHBH198wuxYIoxtvrsBt5EskdTcRjAYXvJv9N5ojd3t+QoT4QXgDi5l0g== -react-input-autosize@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-3.0.0.tgz#6b5898c790d4478d69420b55441fcc31d5c50a85" - integrity sha512-nL9uS7jEs/zu8sqwFE5MAPx6pPkNAriACQ2rGLlqmKr2sPGtN7TXTyDdQt4lbNXVx7Uzadb40x8qotIuru6Rhg== - dependencies: - prop-types "^15.5.8" - react-is@17.0.2, "react-is@^16.12.0 || ^17.0.0", react-is@^17.0.1, react-is@^17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" @@ -6989,17 +7035,17 @@ react-remove-scroll@2.4.1: use-callback-ref "^1.2.3" use-sidecar "^1.0.1" -react-select@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/react-select/-/react-select-4.3.1.tgz#389fc07c9bc7cf7d3c377b7a05ea18cd7399cb81" - integrity sha512-HBBd0dYwkF5aZk1zP81Wx5UsLIIT2lSvAY2JiJo199LjoLHoivjn9//KsmvQMEFGNhe58xyuOITjfxKCcGc62Q== +react-select@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/react-select/-/react-select-5.2.1.tgz#416c25c6b79b94687702374e019c4f2ed9d159d6" + integrity sha512-OOyNzfKrhOcw/BlembyGWgdlJ2ObZRaqmQppPFut1RptJO423j+Y+JIsmxkvsZ4D/3CpOmwIlCvWbbAWEdh12A== dependencies: "@babel/runtime" "^7.12.0" "@emotion/cache" "^11.4.0" "@emotion/react" "^11.1.1" + "@types/react-transition-group" "^4.4.0" memoize-one "^5.0.0" prop-types "^15.6.0" - react-input-autosize "^3.0.0" react-transition-group "^4.3.0" react-shallow-renderer@^16.13.1: @@ -7872,6 +7918,11 @@ ts-pnp@^1.1.6: resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92" integrity sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw== +ts-toolbelt@^9.3.12: + version "9.6.0" + resolved "https://registry.yarnpkg.com/ts-toolbelt/-/ts-toolbelt-9.6.0.tgz#50a25426cfed500d4a09bd1b3afb6f28879edfd5" + integrity sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w== + tsconfig-paths@^3.11.0: version "3.11.0" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.11.0.tgz#954c1fe973da6339c78e06b03ce2e48810b65f36"