mirror of
https://github.com/checktheroads/hyperglass
synced 2024-05-11 05:55:08 +00:00
Upgrade react-select & improve select typing
This commit is contained in:
@@ -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<LocationOption>[] {
|
||||
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<LocationOption> = (options): void => {
|
||||
if (isMultiValue(options)) {
|
||||
onChange({ field: 'queryLocation', value: options.map(o => o.value) });
|
||||
setSelection('queryLocation', options);
|
||||
} else {
|
||||
setSelection<LocationOption>('queryLocation', options);
|
||||
} else if (isSingleValue(options)) {
|
||||
onChange({ field: 'queryLocation', value: options.value });
|
||||
setSelection('queryLocation', [options]);
|
||||
setSelection<LocationOption>('queryLocation', [options]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (element === 'cards') {
|
||||
return (
|
||||
@@ -211,9 +216,8 @@ export const QueryLocation = (props: TQuerySelectField): JSX.Element => {
|
||||
);
|
||||
} else if (element === 'select') {
|
||||
return (
|
||||
<Select
|
||||
<Select<LocationOption, true>
|
||||
isMulti
|
||||
size="lg"
|
||||
options={options}
|
||||
aria-label={label}
|
||||
name="queryLocation"
|
||||
|
@@ -2,40 +2,44 @@ import { useMemo } from 'react';
|
||||
import { Input, InputGroup, InputRightElement, Text } from '@chakra-ui/react';
|
||||
import { components } from 'react-select';
|
||||
import { If, Select } from '~/components';
|
||||
import { isSingleValue } from '~/components/select';
|
||||
import { useColorValue } from '~/context';
|
||||
import { useDirective, useFormState } from '~/hooks';
|
||||
import { isSelectDirective } from '~/types';
|
||||
import { UserIP } from './userIP';
|
||||
|
||||
import type { OptionProps } from 'react-select';
|
||||
import type { OptionProps, GroupBase } from 'react-select';
|
||||
import type { SelectOnChange } from '~/components/select';
|
||||
import type { Directive, SingleOption } from '~/types';
|
||||
import type { TQueryTarget } from './types';
|
||||
|
||||
function buildOptions(directive: Nullable<Directive>): SingleOption[] {
|
||||
type OptionWithDescription = SingleOption<{ description: string | null }>;
|
||||
|
||||
function buildOptions(directive: Nullable<Directive>): OptionWithDescription[] {
|
||||
if (directive !== null && isSelectDirective(directive)) {
|
||||
return directive.options.map(o => ({
|
||||
value: o.value,
|
||||
label: o.name,
|
||||
description: o.description,
|
||||
data: { description: o.description },
|
||||
}));
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
const Option = (props: OptionProps<Dict, false>) => {
|
||||
const Option = (props: OptionProps<OptionWithDescription, false>) => {
|
||||
const { label, data } = props;
|
||||
return (
|
||||
<components.Option {...props}>
|
||||
<components.Option<OptionWithDescription, false, GroupBase<OptionWithDescription>> {...props}>
|
||||
<Text as="span">{label}</Text>
|
||||
<br />
|
||||
<Text fontSize="xs" as="span">
|
||||
{data.description}
|
||||
{data.data?.description}
|
||||
</Text>
|
||||
</components.Option>
|
||||
);
|
||||
};
|
||||
|
||||
export const QueryTarget: React.FC<TQueryTarget> = (props: TQueryTarget) => {
|
||||
export const QueryTarget = (props: TQueryTarget): JSX.Element => {
|
||||
const { name, register, onChange, placeholder } = props;
|
||||
|
||||
const bg = useColorValue('white', 'whiteAlpha.100');
|
||||
@@ -54,22 +58,20 @@ export const QueryTarget: React.FC<TQueryTarget> = (props: TQueryTarget) => {
|
||||
onChange({ field: name, value: e.target.value });
|
||||
}
|
||||
|
||||
function handleSelectChange(e: SingleOption | SingleOption[]): void {
|
||||
if (!Array.isArray(e) && e !== null) {
|
||||
const handleSelectChange: SelectOnChange<OptionWithDescription> = e => {
|
||||
if (isSingleValue(e)) {
|
||||
onChange({ field: name, value: e.value });
|
||||
setTarget({ display: e.value });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<input {...register('queryTarget')} hidden readOnly value={form.queryTarget} />
|
||||
<If c={directive !== null && isSelectDirective(directive)}>
|
||||
<Select
|
||||
size="lg"
|
||||
<Select<OptionWithDescription, false>
|
||||
name={name}
|
||||
options={options}
|
||||
innerRef={register}
|
||||
components={{ Option }}
|
||||
onChange={handleSelectChange}
|
||||
/>
|
||||
|
@@ -4,22 +4,25 @@ import { Box, Button, HStack, useRadio, useRadioGroup } from '@chakra-ui/react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { components } from 'react-select';
|
||||
import { Select } from '~/components';
|
||||
import { useFormState } from '~/hooks';
|
||||
import { useFormState, useFormSelections } from '~/hooks';
|
||||
import { isSingleValue } from '~/components/select';
|
||||
|
||||
import type { UseRadioProps } from '@chakra-ui/react';
|
||||
import type { MenuListComponentProps } from 'react-select';
|
||||
import type { SingleOption, OptionGroup, SelectOption } from '~/types';
|
||||
import type { TOptions } from '~/components/select';
|
||||
import type { MenuListProps } from 'react-select';
|
||||
import type { SingleOption, OptionGroup, OptionsOrGroup } from '~/types';
|
||||
import type { SelectOnChange } from '~/components/select';
|
||||
import type { TQuerySelectField } from './types';
|
||||
|
||||
function sorter<T extends SingleOption | OptionGroup>(a: T, b: T): number {
|
||||
type QueryTypeOption = SingleOption<{ group?: string }>;
|
||||
|
||||
function sorter<T extends QueryTypeOption | OptionGroup<QueryTypeOption>>(a: T, b: T): number {
|
||||
return a.label < b.label ? -1 : a.label > b.label ? 1 : 0;
|
||||
}
|
||||
|
||||
type UserFilter = {
|
||||
selected: string;
|
||||
setSelected(n: string): void;
|
||||
filter(candidate: SelectOption<{ group: string | null }>, input: string): boolean;
|
||||
filter(candidate: QueryTypeOption, input: string): boolean;
|
||||
};
|
||||
|
||||
const useFilter = create<UserFilter>((set, get) => ({
|
||||
@@ -28,10 +31,9 @@ const useFilter = create<UserFilter>((set, get) => ({
|
||||
set(() => ({ selected: newValue }));
|
||||
},
|
||||
filter(candidate, input): boolean {
|
||||
const {
|
||||
label,
|
||||
data: { group },
|
||||
} = candidate;
|
||||
const { label, data } = candidate;
|
||||
const group = data?.group ?? null;
|
||||
|
||||
if (input && (label || group)) {
|
||||
const search = input.toLowerCase();
|
||||
if (group) {
|
||||
@@ -52,14 +54,14 @@ const useFilter = create<UserFilter>((set, get) => ({
|
||||
|
||||
function useOptions() {
|
||||
const filtered = useFormState(s => s.filtered);
|
||||
return useMemo((): TOptions => {
|
||||
return useMemo((): OptionsOrGroup<QueryTypeOption> => {
|
||||
const groupNames = new Set(
|
||||
filtered.types
|
||||
.filter(t => t.groups.length > 0)
|
||||
.map(t => t.groups)
|
||||
.flat(),
|
||||
);
|
||||
const optGroups: OptionGroup[] = Array.from(groupNames).map(group => ({
|
||||
const optGroups: OptionGroup<QueryTypeOption>[] = Array.from(groupNames).map(group => ({
|
||||
label: group,
|
||||
options: filtered.types
|
||||
.filter(t => t.groups.includes(group))
|
||||
@@ -67,7 +69,7 @@ function useOptions() {
|
||||
.sort(sorter),
|
||||
}));
|
||||
|
||||
const noGroups: OptionGroup = {
|
||||
const noGroups: OptionGroup<QueryTypeOption> = {
|
||||
label: '',
|
||||
options: filtered.types
|
||||
.filter(t => t.groups.length === 0)
|
||||
@@ -108,7 +110,7 @@ const GroupFilter = (props: React.PropsWithChildren<UseRadioProps>): JSX.Element
|
||||
);
|
||||
};
|
||||
|
||||
const MenuList = (props: MenuListComponentProps<TOptions, false>) => {
|
||||
const MenuList = (props: MenuListProps<QueryTypeOption, boolean>): JSX.Element => {
|
||||
const { children, ...rest } = props;
|
||||
const filtered = useFormState(s => s.filtered);
|
||||
const selected = useFilter(state => state.selected);
|
||||
@@ -150,27 +152,25 @@ export const QueryType = (props: TQuerySelectField): JSX.Element => {
|
||||
formState: { errors },
|
||||
} = useFormContext();
|
||||
const setSelection = useFormState(s => s.setSelection);
|
||||
const selections = useFormState(s => s.selections);
|
||||
const selections = useFormSelections<QueryTypeOption>();
|
||||
const setFormValue = useFormState(s => s.setFormValue);
|
||||
const options = useOptions();
|
||||
const { filter } = useFilter(); // Intentionally re-render on any changes
|
||||
|
||||
function handleChange(e: SingleOption | SingleOption[]): void {
|
||||
const handleChange: SelectOnChange<QueryTypeOption> = e => {
|
||||
let value = '';
|
||||
if (!Array.isArray(e) && e !== null) {
|
||||
// setFormValue('queryType', e.value);
|
||||
setSelection('queryType', e);
|
||||
if (isSingleValue(e)) {
|
||||
setSelection<QueryTypeOption>('queryType', e);
|
||||
value = e.value;
|
||||
} else {
|
||||
setFormValue('queryType', '');
|
||||
setSelection('queryType', null);
|
||||
setSelection<QueryTypeOption>('queryType', null);
|
||||
}
|
||||
onChange({ field: 'queryType', value });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Select
|
||||
size="lg"
|
||||
<Select<QueryTypeOption>
|
||||
name="queryType"
|
||||
options={options}
|
||||
aria-label={label}
|
||||
|
@@ -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(() => {
|
||||
|
@@ -1,2 +1,3 @@
|
||||
export * from './select';
|
||||
export type { TOptions } from './types';
|
||||
export { isSingleValue, isMultiValue } from './types';
|
||||
export type { SelectOnChange } from './types';
|
||||
|
@@ -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 = <Opt extends SingleOption, IsMulti extends boolean>(
|
||||
props: OptionProps<Opt, IsMulti>,
|
||||
): JSX.Element => {
|
||||
const { label, data } = props;
|
||||
const tags = Array.isArray(data.tags) ? (data.tags as string[]) : [];
|
||||
return (
|
||||
<components.Option {...props}>
|
||||
<Box as="span" d={{ base: 'block', lg: 'inline' }}>
|
||||
{label}
|
||||
</Box>
|
||||
<components.Option<Opt, IsMulti, GroupBase<Opt>> {...props}>
|
||||
<chakra.span d={{ base: 'block', lg: 'inline' }}>{label}</chakra.span>
|
||||
{tags.length > 0 && (
|
||||
<HStack d={{ base: 'flex', lg: 'inline-flex' }} ms={{ base: 0, lg: 2 }} alignItems="center">
|
||||
{tags.map(tag => (
|
||||
|
@@ -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<TSelectContext>({} as TSelectContext);
|
||||
export const useSelectContext = (): TSelectContext => useContext(SelectContext);
|
||||
|
||||
const ReactSelectChakra = chakra<typeof ReactSelect, TReactSelectChakra>(ReactSelect);
|
||||
export const Select = forwardRef(
|
||||
<Opt extends SingleOption = SingleOption, IsMulti extends boolean = boolean>(
|
||||
props: TSelectBase<Opt, IsMulti>,
|
||||
ref: React.Ref<SelectInstance<Opt, IsMulti>>,
|
||||
): JSX.Element => {
|
||||
const { options, isMulti, onSelect, isError = false, components, ...rest } = props;
|
||||
|
||||
export const Select: React.FC<TSelectBase> = (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<TSelectContext>(
|
||||
() => ({ colorMode, isOpen, isError }),
|
||||
[colorMode, isError, isOpen],
|
||||
);
|
||||
const defaultOnChange: ReactSelectProps<Opt, IsMulti>['onChange'] = changed => {
|
||||
if (isSingleValue<Opt>(changed)) {
|
||||
changed = [changed] as unknown as OnChangeValue<Opt, IsMulti>;
|
||||
}
|
||||
if (typeof onSelect === 'function') {
|
||||
onSelect(changed as MultiValue<Opt>);
|
||||
}
|
||||
};
|
||||
|
||||
const defaultOnChange = (changed: SingleOption | SingleOption[]) => {
|
||||
if (!Array.isArray(changed)) {
|
||||
changed = [changed];
|
||||
}
|
||||
if (typeof onSelect === 'function') {
|
||||
onSelect(changed);
|
||||
}
|
||||
};
|
||||
const menu = useMenuStyle<Opt, IsMulti>({ colorMode });
|
||||
const menuList = useMenuListStyle<Opt, IsMulti>({ colorMode });
|
||||
const control = useControlStyle<Opt, IsMulti>({ colorMode });
|
||||
const option = useOptionStyle<Opt, IsMulti>({ colorMode });
|
||||
const singleValue = useSingleValueStyle<Opt, IsMulti>({ colorMode });
|
||||
const multiValue = useMultiValueStyle<Opt, IsMulti>({ colorMode });
|
||||
const multiValueLabel = useMultiValueLabelStyle<Opt, IsMulti>({ colorMode });
|
||||
const multiValueRemove = useMultiValueRemoveStyle<Opt, IsMulti>({ colorMode });
|
||||
const menuPortal = useMenuPortal<Opt, IsMulti>();
|
||||
const placeholder = usePlaceholderStyle<Opt, IsMulti>({ colorMode });
|
||||
const indicatorSeparator = useIndicatorSeparatorStyle<Opt, IsMulti>({ colorMode });
|
||||
const rsTheme = useRSTheme();
|
||||
|
||||
const multiValue = useMultiValueStyle({ colorMode });
|
||||
const multiValueLabel = useMultiValueLabelStyle({ colorMode });
|
||||
const multiValueRemove = useMultiValueRemoveStyle({ colorMode });
|
||||
const menuPortal = useMenuPortal();
|
||||
const rsTheme = useRSTheme();
|
||||
|
||||
return (
|
||||
<SelectContext.Provider value={selectContext}>
|
||||
<ReactSelectChakra
|
||||
onChange={defaultOnChange}
|
||||
onMenuClose={onClose}
|
||||
onMenuOpen={onOpen}
|
||||
isClearable={true}
|
||||
options={options}
|
||||
isMulti={multi}
|
||||
theme={rsTheme}
|
||||
components={{ Option, ...components }}
|
||||
styles={{
|
||||
menuPortal,
|
||||
multiValue,
|
||||
multiValueLabel,
|
||||
multiValueRemove,
|
||||
menu: useMenuStyle,
|
||||
option: useOptionStyle,
|
||||
control: useControlStyle,
|
||||
menuList: useMenuListStyle,
|
||||
singleValue: useSingleValueStyle,
|
||||
placeholder: usePlaceholderStyle,
|
||||
indicatorSeparator: useIndicatorSeparatorStyle,
|
||||
}}
|
||||
{...rest}
|
||||
/>
|
||||
</SelectContext.Provider>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<SelectContext.Provider value={{ colorMode, isOpen, isError }}>
|
||||
<ReactSelect<Opt, IsMulti>
|
||||
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}
|
||||
/>
|
||||
</SelectContext.Provider>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
219
hyperglass/ui/components/select/styles.ts
Normal file
219
hyperglass/ui/components/select/styles.ts
Normal file
@@ -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 = <Opt extends SingleOption, IsMulti extends boolean>(
|
||||
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 = <Opt extends SingleOption, IsMulti extends boolean>(
|
||||
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 = <Opt extends SingleOption, IsMulti extends boolean>(
|
||||
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 = <Opt extends SingleOption, IsMulti extends boolean>(
|
||||
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 = <Opt extends SingleOption, IsMulti extends boolean>(
|
||||
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 = <Opt extends SingleOption, IsMulti extends boolean>(
|
||||
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 = <Opt extends SingleOption, IsMulti extends boolean>(
|
||||
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 = <Opt extends SingleOption, IsMulti extends boolean>(
|
||||
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 = <Opt extends SingleOption, IsMulti extends boolean>(
|
||||
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 = <Opt extends SingleOption, IsMulti extends boolean>(
|
||||
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 = <Opt extends SingleOption, IsMulti extends boolean>(): RSStyleFunction<
|
||||
'menuPortal',
|
||||
Opt,
|
||||
IsMulti
|
||||
> => {
|
||||
const isMobile = useMobile();
|
||||
const styles = {
|
||||
zIndex: isMobile ? 1500 : 1,
|
||||
};
|
||||
return useCallback(base => merge(base, styles), [isMobile]);
|
||||
};
|
@@ -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]);
|
||||
};
|
@@ -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<ReactSelect.Props<Opt, IsMulti>['onChange']>;
|
||||
|
||||
export type TOptions = Array<SingleOption | OptionGroup>;
|
||||
|
||||
export type TReactSelectChakra = Omit<IReactSelect, 'isMulti' | 'onSelect' | 'onChange'> &
|
||||
Omit<BoxProps, 'onChange' | 'onSelect'>;
|
||||
|
||||
export interface TSelectBase extends TReactSelectChakra {
|
||||
export interface TSelectBase<Opt extends SingleOption, IsMulti extends boolean>
|
||||
extends ReactSelect.Props<Opt, IsMulti> {
|
||||
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<Opt>) => 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<RSTheme, 'borderRadius'> {
|
||||
borderRadius: string | number;
|
||||
type StyleConfigKeys = keyof ReactSelect.StylesConfig<
|
||||
SingleOption,
|
||||
boolean,
|
||||
ReactSelect.GroupBase<SingleOption>
|
||||
>;
|
||||
|
||||
export type RSStyleFunction<
|
||||
K extends StyleConfigKeys,
|
||||
Opt extends SingleOption,
|
||||
IsMulti extends boolean,
|
||||
> = StylesConfigFunction<StylesProps<Opt, IsMulti, ReactSelect.GroupBase<Opt>>[K]>;
|
||||
|
||||
export type RSThemeFunction = (theme: ReactSelect.Theme) => ReactSelect.Theme;
|
||||
|
||||
export function isSingleValue<Opt extends SingleOption>(
|
||||
value: ReactSelect.SingleValue<Opt> | ReactSelect.MultiValue<Opt>,
|
||||
): value is NonNullable<ReactSelect.SingleValue<Opt>> {
|
||||
return value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
export type TControl = ControlProps<TOptions, false>;
|
||||
|
||||
export type TMenu = MenuProps<TOptions, false>;
|
||||
|
||||
export type TMenuList = MenuListComponentProps<TOptions, false>;
|
||||
|
||||
export type TOption = OptionProps<TOptions, false>;
|
||||
|
||||
export type TMultiValueState = MultiValueProps<TOptions>;
|
||||
|
||||
export type TIndicator = IndicatorProps<TOptions, false>;
|
||||
|
||||
export type TPlaceholder = PlaceholderProps<TOptions, false>;
|
||||
|
||||
export type TMultiValue = Pick<TSelectContext, 'colorMode'>;
|
||||
|
||||
export type TRSStyleCallback = (base: TStyles) => TStyles;
|
||||
|
||||
export type TRSThemeCallback = (theme: TRSTheme) => TRSTheme;
|
||||
|
||||
export type TStyles = RSStyles<TOptions, false>;
|
||||
export function isMultiValue<Opt extends SingleOption>(
|
||||
value: ReactSelect.SingleValue<Opt> | ReactSelect.MultiValue<Opt>,
|
||||
): value is NonNullable<ReactSelect.MultiValue<Opt>> {
|
||||
return value !== null && Array.isArray(value);
|
||||
}
|
||||
|
@@ -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<Opt extends SingleOption = SingleOption> {
|
||||
queryLocation: MultiValue<Opt>;
|
||||
queryType: SingleValue<Opt>;
|
||||
}
|
||||
|
||||
interface Filtered {
|
||||
@@ -39,13 +40,13 @@ interface Target {
|
||||
display: string;
|
||||
}
|
||||
|
||||
interface FormStateType {
|
||||
interface FormStateType<Opt extends SingleOption = SingleOption> {
|
||||
// Values
|
||||
filtered: Filtered;
|
||||
form: FormValues;
|
||||
loading: boolean;
|
||||
responses: Responses;
|
||||
selections: FormSelections;
|
||||
selections: FormSelections<Opt>;
|
||||
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<K extends keyof FormSelections>(field: K, value: FormSelections[K]): void;
|
||||
setSelection<
|
||||
Opt extends SingleOption,
|
||||
K extends keyof FormSelections<Opt> = keyof FormSelections<Opt>,
|
||||
>(
|
||||
field: K,
|
||||
value: FormSelections[K],
|
||||
): void;
|
||||
setTarget(update: Partial<Target>): void;
|
||||
getDirective(): Directive | null;
|
||||
reset(): void;
|
||||
@@ -95,7 +102,10 @@ const formState: StateCreator<FormStateType> = (set, get) => ({
|
||||
set({ status });
|
||||
},
|
||||
|
||||
setSelection<K extends keyof FormSelections>(field: K, value: FormSelections[K]): void {
|
||||
setSelection<
|
||||
Opt extends SingleOption,
|
||||
K extends keyof FormSelections<Opt> = keyof FormSelections<Opt>,
|
||||
>(field: K, value: FormSelections[K]): void {
|
||||
set(state => ({ selections: { ...state.selections, [field]: value } }));
|
||||
},
|
||||
|
||||
@@ -223,6 +233,10 @@ export const useFormState = create<FormStateType>(
|
||||
withDev<FormStateType>(formState, 'useFormState'),
|
||||
);
|
||||
|
||||
export function useFormSelections<Opt extends SingleOption = SingleOption>(): FormSelections<Opt> {
|
||||
return useFormState(s => s.selections as FormSelections<Opt>);
|
||||
}
|
||||
|
||||
export function useView(): FormStatus {
|
||||
const { status, form } = useFormState(({ status, form }) => ({ status, form }));
|
||||
return useMemo(() => {
|
||||
|
@@ -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],
|
||||
);
|
||||
}
|
||||
|
@@ -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]));
|
||||
});
|
||||
}
|
||||
|
9
hyperglass/ui/package.json
vendored
9
hyperglass/ui/package.json
vendored
@@ -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",
|
||||
|
@@ -1,18 +1,19 @@
|
||||
type AnyOption = {
|
||||
interface AnyOption {
|
||||
label: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type SingleOption = AnyOption & {
|
||||
export interface SingleOption<T extends Record<string, unknown> = Record<string, unknown>>
|
||||
extends AnyOption {
|
||||
value: string;
|
||||
group?: string;
|
||||
tags?: string[];
|
||||
data?: Record<string, unknown>;
|
||||
};
|
||||
data?: T;
|
||||
}
|
||||
|
||||
export type OptionGroup = AnyOption & {
|
||||
options: SingleOption[];
|
||||
};
|
||||
export interface OptionGroup<Opt extends SingleOption> extends AnyOption {
|
||||
options: Opt[];
|
||||
}
|
||||
|
||||
export type SelectOption<T extends unknown = unknown> = (SingleOption | OptionGroup) & { data: T };
|
||||
export type OptionsOrGroup<Opt extends SingleOption> = Array<Opt | OptionGroup<Opt>>;
|
||||
|
||||
export type OnChangeArgs = { field: string; value: string | string[] };
|
||||
|
8
hyperglass/ui/types/globals.d.ts
vendored
8
hyperglass/ui/types/globals.d.ts
vendored
@@ -61,3 +61,11 @@ declare global {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'react' {
|
||||
// Enable generic typing with forwardRef.
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
function forwardRef<T, P = {}>(
|
||||
render: (props: P, ref: React.Ref<T>) => React.ReactElement | null,
|
||||
): (props: P & React.RefAttributes<T>) => React.ReactElement | null;
|
||||
}
|
||||
|
117
hyperglass/ui/yarn.lock
vendored
117
hyperglass/ui/yarn.lock
vendored
@@ -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"
|
||||
|
Reference in New Issue
Block a user