forked from mirrors/thatmattlove-hyperglass
Upgrade react-select & improve select typing
This commit is contained in:
parent
7c73b2b9a1
commit
6afe23bd17
17 changed files with 554 additions and 429 deletions
|
|
@ -5,11 +5,16 @@ import { useFormContext } from 'react-hook-form';
|
||||||
import { Select } from '~/components';
|
import { Select } from '~/components';
|
||||||
import { useConfig, useColorValue } from '~/context';
|
import { useConfig, useColorValue } from '~/context';
|
||||||
import { useOpposingColor, useFormState } from '~/hooks';
|
import { useOpposingColor, useFormState } from '~/hooks';
|
||||||
|
import { isMultiValue, isSingleValue } from '~/components/select';
|
||||||
|
|
||||||
import type { DeviceGroup, SingleOption, OptionGroup, FormData } from '~/types';
|
import type { DeviceGroup, SingleOption, OptionGroup, FormData } from '~/types';
|
||||||
|
import type { SelectOnChange } from '~/components/select';
|
||||||
import type { TQuerySelectField, LocationCardProps } from './types';
|
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
|
return devices
|
||||||
.map(group => {
|
.map(group => {
|
||||||
const label = group.group;
|
const label = group.group;
|
||||||
|
|
@ -39,7 +44,7 @@ const LocationCard = (props: LocationCardProps): JSX.Element => {
|
||||||
const { label } = option;
|
const { label } = option;
|
||||||
const [isChecked, setChecked] = useState(defaultChecked);
|
const [isChecked, setChecked] = useState(defaultChecked);
|
||||||
|
|
||||||
function handleChange(value: SingleOption) {
|
function handleChange(value: LocationOption) {
|
||||||
if (isChecked) {
|
if (isChecked) {
|
||||||
setChecked(false);
|
setChecked(false);
|
||||||
onChange('remove', value);
|
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
|
* @param options Final value. React-select determines if an option is being added or removed and
|
||||||
* only sends back the final value.
|
* only sends back the final value.
|
||||||
*/
|
*/
|
||||||
function handleSelectChange(options: SingleOption[] | SingleOption): void {
|
const handleSelectChange: SelectOnChange<LocationOption> = (options): void => {
|
||||||
if (Array.isArray(options)) {
|
if (isMultiValue(options)) {
|
||||||
onChange({ field: 'queryLocation', value: options.map(o => o.value) });
|
onChange({ field: 'queryLocation', value: options.map(o => o.value) });
|
||||||
setSelection('queryLocation', options);
|
setSelection<LocationOption>('queryLocation', options);
|
||||||
} else {
|
} else if (isSingleValue(options)) {
|
||||||
onChange({ field: 'queryLocation', value: options.value });
|
onChange({ field: 'queryLocation', value: options.value });
|
||||||
setSelection('queryLocation', [options]);
|
setSelection<LocationOption>('queryLocation', [options]);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
if (element === 'cards') {
|
if (element === 'cards') {
|
||||||
return (
|
return (
|
||||||
|
|
@ -211,9 +216,8 @@ export const QueryLocation = (props: TQuerySelectField): JSX.Element => {
|
||||||
);
|
);
|
||||||
} else if (element === 'select') {
|
} else if (element === 'select') {
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select<LocationOption, true>
|
||||||
isMulti
|
isMulti
|
||||||
size="lg"
|
|
||||||
options={options}
|
options={options}
|
||||||
aria-label={label}
|
aria-label={label}
|
||||||
name="queryLocation"
|
name="queryLocation"
|
||||||
|
|
|
||||||
|
|
@ -2,40 +2,44 @@ import { useMemo } from 'react';
|
||||||
import { Input, InputGroup, InputRightElement, Text } from '@chakra-ui/react';
|
import { Input, InputGroup, InputRightElement, Text } from '@chakra-ui/react';
|
||||||
import { components } from 'react-select';
|
import { components } from 'react-select';
|
||||||
import { If, Select } from '~/components';
|
import { If, Select } from '~/components';
|
||||||
|
import { isSingleValue } from '~/components/select';
|
||||||
import { useColorValue } from '~/context';
|
import { useColorValue } from '~/context';
|
||||||
import { useDirective, useFormState } from '~/hooks';
|
import { useDirective, useFormState } from '~/hooks';
|
||||||
import { isSelectDirective } from '~/types';
|
import { isSelectDirective } from '~/types';
|
||||||
import { UserIP } from './userIP';
|
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 { Directive, SingleOption } from '~/types';
|
||||||
import type { TQueryTarget } 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)) {
|
if (directive !== null && isSelectDirective(directive)) {
|
||||||
return directive.options.map(o => ({
|
return directive.options.map(o => ({
|
||||||
value: o.value,
|
value: o.value,
|
||||||
label: o.name,
|
label: o.name,
|
||||||
description: o.description,
|
data: { description: o.description },
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const Option = (props: OptionProps<Dict, false>) => {
|
const Option = (props: OptionProps<OptionWithDescription, false>) => {
|
||||||
const { label, data } = props;
|
const { label, data } = props;
|
||||||
return (
|
return (
|
||||||
<components.Option {...props}>
|
<components.Option<OptionWithDescription, false, GroupBase<OptionWithDescription>> {...props}>
|
||||||
<Text as="span">{label}</Text>
|
<Text as="span">{label}</Text>
|
||||||
<br />
|
<br />
|
||||||
<Text fontSize="xs" as="span">
|
<Text fontSize="xs" as="span">
|
||||||
{data.description}
|
{data.data?.description}
|
||||||
</Text>
|
</Text>
|
||||||
</components.Option>
|
</components.Option>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const QueryTarget: React.FC<TQueryTarget> = (props: TQueryTarget) => {
|
export const QueryTarget = (props: TQueryTarget): JSX.Element => {
|
||||||
const { name, register, onChange, placeholder } = props;
|
const { name, register, onChange, placeholder } = props;
|
||||||
|
|
||||||
const bg = useColorValue('white', 'whiteAlpha.100');
|
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 });
|
onChange({ field: name, value: e.target.value });
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSelectChange(e: SingleOption | SingleOption[]): void {
|
const handleSelectChange: SelectOnChange<OptionWithDescription> = e => {
|
||||||
if (!Array.isArray(e) && e !== null) {
|
if (isSingleValue(e)) {
|
||||||
onChange({ field: name, value: e.value });
|
onChange({ field: name, value: e.value });
|
||||||
setTarget({ display: e.value });
|
setTarget({ display: e.value });
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<input {...register('queryTarget')} hidden readOnly value={form.queryTarget} />
|
<input {...register('queryTarget')} hidden readOnly value={form.queryTarget} />
|
||||||
<If c={directive !== null && isSelectDirective(directive)}>
|
<If c={directive !== null && isSelectDirective(directive)}>
|
||||||
<Select
|
<Select<OptionWithDescription, false>
|
||||||
size="lg"
|
|
||||||
name={name}
|
name={name}
|
||||||
options={options}
|
options={options}
|
||||||
innerRef={register}
|
|
||||||
components={{ Option }}
|
components={{ Option }}
|
||||||
onChange={handleSelectChange}
|
onChange={handleSelectChange}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -4,22 +4,25 @@ import { Box, Button, HStack, useRadio, useRadioGroup } from '@chakra-ui/react';
|
||||||
import { useFormContext } from 'react-hook-form';
|
import { useFormContext } from 'react-hook-form';
|
||||||
import { components } from 'react-select';
|
import { components } from 'react-select';
|
||||||
import { Select } from '~/components';
|
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 { UseRadioProps } from '@chakra-ui/react';
|
||||||
import type { MenuListComponentProps } from 'react-select';
|
import type { MenuListProps } from 'react-select';
|
||||||
import type { SingleOption, OptionGroup, SelectOption } from '~/types';
|
import type { SingleOption, OptionGroup, OptionsOrGroup } from '~/types';
|
||||||
import type { TOptions } from '~/components/select';
|
import type { SelectOnChange } from '~/components/select';
|
||||||
import type { TQuerySelectField } from './types';
|
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;
|
return a.label < b.label ? -1 : a.label > b.label ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserFilter = {
|
type UserFilter = {
|
||||||
selected: string;
|
selected: string;
|
||||||
setSelected(n: string): void;
|
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) => ({
|
const useFilter = create<UserFilter>((set, get) => ({
|
||||||
|
|
@ -28,10 +31,9 @@ const useFilter = create<UserFilter>((set, get) => ({
|
||||||
set(() => ({ selected: newValue }));
|
set(() => ({ selected: newValue }));
|
||||||
},
|
},
|
||||||
filter(candidate, input): boolean {
|
filter(candidate, input): boolean {
|
||||||
const {
|
const { label, data } = candidate;
|
||||||
label,
|
const group = data?.group ?? null;
|
||||||
data: { group },
|
|
||||||
} = candidate;
|
|
||||||
if (input && (label || group)) {
|
if (input && (label || group)) {
|
||||||
const search = input.toLowerCase();
|
const search = input.toLowerCase();
|
||||||
if (group) {
|
if (group) {
|
||||||
|
|
@ -52,14 +54,14 @@ const useFilter = create<UserFilter>((set, get) => ({
|
||||||
|
|
||||||
function useOptions() {
|
function useOptions() {
|
||||||
const filtered = useFormState(s => s.filtered);
|
const filtered = useFormState(s => s.filtered);
|
||||||
return useMemo((): TOptions => {
|
return useMemo((): OptionsOrGroup<QueryTypeOption> => {
|
||||||
const groupNames = new Set(
|
const groupNames = new Set(
|
||||||
filtered.types
|
filtered.types
|
||||||
.filter(t => t.groups.length > 0)
|
.filter(t => t.groups.length > 0)
|
||||||
.map(t => t.groups)
|
.map(t => t.groups)
|
||||||
.flat(),
|
.flat(),
|
||||||
);
|
);
|
||||||
const optGroups: OptionGroup[] = Array.from(groupNames).map(group => ({
|
const optGroups: OptionGroup<QueryTypeOption>[] = Array.from(groupNames).map(group => ({
|
||||||
label: group,
|
label: group,
|
||||||
options: filtered.types
|
options: filtered.types
|
||||||
.filter(t => t.groups.includes(group))
|
.filter(t => t.groups.includes(group))
|
||||||
|
|
@ -67,7 +69,7 @@ function useOptions() {
|
||||||
.sort(sorter),
|
.sort(sorter),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const noGroups: OptionGroup = {
|
const noGroups: OptionGroup<QueryTypeOption> = {
|
||||||
label: '',
|
label: '',
|
||||||
options: filtered.types
|
options: filtered.types
|
||||||
.filter(t => t.groups.length === 0)
|
.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 { children, ...rest } = props;
|
||||||
const filtered = useFormState(s => s.filtered);
|
const filtered = useFormState(s => s.filtered);
|
||||||
const selected = useFilter(state => state.selected);
|
const selected = useFilter(state => state.selected);
|
||||||
|
|
@ -150,27 +152,25 @@ export const QueryType = (props: TQuerySelectField): JSX.Element => {
|
||||||
formState: { errors },
|
formState: { errors },
|
||||||
} = useFormContext();
|
} = useFormContext();
|
||||||
const setSelection = useFormState(s => s.setSelection);
|
const setSelection = useFormState(s => s.setSelection);
|
||||||
const selections = useFormState(s => s.selections);
|
const selections = useFormSelections<QueryTypeOption>();
|
||||||
const setFormValue = useFormState(s => s.setFormValue);
|
const setFormValue = useFormState(s => s.setFormValue);
|
||||||
const options = useOptions();
|
const options = useOptions();
|
||||||
const { filter } = useFilter(); // Intentionally re-render on any changes
|
const { filter } = useFilter(); // Intentionally re-render on any changes
|
||||||
|
|
||||||
function handleChange(e: SingleOption | SingleOption[]): void {
|
const handleChange: SelectOnChange<QueryTypeOption> = e => {
|
||||||
let value = '';
|
let value = '';
|
||||||
if (!Array.isArray(e) && e !== null) {
|
if (isSingleValue(e)) {
|
||||||
// setFormValue('queryType', e.value);
|
setSelection<QueryTypeOption>('queryType', e);
|
||||||
setSelection('queryType', e);
|
|
||||||
value = e.value;
|
value = e.value;
|
||||||
} else {
|
} else {
|
||||||
setFormValue('queryType', '');
|
setFormValue('queryType', '');
|
||||||
setSelection('queryType', null);
|
setSelection<QueryTypeOption>('queryType', null);
|
||||||
}
|
}
|
||||||
onChange({ field: 'queryType', value });
|
onChange({ field: 'queryType', value });
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select<QueryTypeOption>
|
||||||
size="lg"
|
|
||||||
name="queryType"
|
name="queryType"
|
||||||
options={options}
|
options={options}
|
||||||
aria-label={label}
|
aria-label={label}
|
||||||
|
|
|
||||||
|
|
@ -160,6 +160,7 @@ export const LookingGlass = (): JSX.Element => {
|
||||||
} else if (e.field === 'queryTarget' && isString(e.value)) {
|
} else if (e.field === 'queryTarget' && isString(e.value)) {
|
||||||
setFormValue('queryTarget', e.value);
|
setFormValue('queryTarget', e.value);
|
||||||
}
|
}
|
||||||
|
console.table(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
export * from './select';
|
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 { 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 { label, data } = props;
|
||||||
const tags = Array.isArray(data.tags) ? (data.tags as string[]) : [];
|
const tags = Array.isArray(data.tags) ? (data.tags as string[]) : [];
|
||||||
return (
|
return (
|
||||||
<components.Option {...props}>
|
<components.Option<Opt, IsMulti, GroupBase<Opt>> {...props}>
|
||||||
<Box as="span" d={{ base: 'block', lg: 'inline' }}>
|
<chakra.span d={{ base: 'block', lg: 'inline' }}>{label}</chakra.span>
|
||||||
{label}
|
|
||||||
</Box>
|
|
||||||
{tags.length > 0 && (
|
{tags.length > 0 && (
|
||||||
<HStack d={{ base: 'flex', lg: 'inline-flex' }} ms={{ base: 0, lg: 2 }} alignItems="center">
|
<HStack d={{ base: 'flex', lg: 'inline-flex' }} ms={{ base: 0, lg: 2 }} alignItems="center">
|
||||||
{tags.map(tag => (
|
{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 ReactSelect from 'react-select';
|
||||||
import { chakra, useDisclosure } from '@chakra-ui/react';
|
import { useDisclosure } from '@chakra-ui/react';
|
||||||
import { useColorMode } from '~/context';
|
import { useColorMode } from '~/context';
|
||||||
import { Option } from './option';
|
import { Option } from './option';
|
||||||
import {
|
import {
|
||||||
|
|
@ -17,67 +17,81 @@ import {
|
||||||
useMultiValueRemoveStyle,
|
useMultiValueRemoveStyle,
|
||||||
useIndicatorSeparatorStyle,
|
useIndicatorSeparatorStyle,
|
||||||
} from './styles';
|
} from './styles';
|
||||||
|
import { isSingleValue } from './types';
|
||||||
|
|
||||||
|
import type {
|
||||||
|
Props as ReactSelectProps,
|
||||||
|
MultiValue,
|
||||||
|
OnChangeValue,
|
||||||
|
SelectInstance,
|
||||||
|
} from 'react-select';
|
||||||
import type { SingleOption } from '~/types';
|
import type { SingleOption } from '~/types';
|
||||||
import type { TSelectBase, TSelectContext, TReactSelectChakra } from './types';
|
import type { TSelectBase, TSelectContext } from './types';
|
||||||
|
|
||||||
const SelectContext = createContext<TSelectContext>({} as TSelectContext);
|
const SelectContext = createContext<TSelectContext>({} as TSelectContext);
|
||||||
export const useSelectContext = (): TSelectContext => useContext(SelectContext);
|
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 { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
const { options, multi, onSelect, isError = false, components, ...rest } = props;
|
|
||||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
|
||||||
|
|
||||||
const { colorMode } = useColorMode();
|
const { colorMode } = useColorMode();
|
||||||
|
|
||||||
const selectContext = useMemo<TSelectContext>(
|
const defaultOnChange: ReactSelectProps<Opt, IsMulti>['onChange'] = changed => {
|
||||||
() => ({ colorMode, isOpen, isError }),
|
if (isSingleValue<Opt>(changed)) {
|
||||||
[colorMode, isError, isOpen],
|
changed = [changed] as unknown as OnChangeValue<Opt, IsMulti>;
|
||||||
);
|
}
|
||||||
|
if (typeof onSelect === 'function') {
|
||||||
|
onSelect(changed as MultiValue<Opt>);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const defaultOnChange = (changed: SingleOption | SingleOption[]) => {
|
const menu = useMenuStyle<Opt, IsMulti>({ colorMode });
|
||||||
if (!Array.isArray(changed)) {
|
const menuList = useMenuListStyle<Opt, IsMulti>({ colorMode });
|
||||||
changed = [changed];
|
const control = useControlStyle<Opt, IsMulti>({ colorMode });
|
||||||
}
|
const option = useOptionStyle<Opt, IsMulti>({ colorMode });
|
||||||
if (typeof onSelect === 'function') {
|
const singleValue = useSingleValueStyle<Opt, IsMulti>({ colorMode });
|
||||||
onSelect(changed);
|
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 });
|
return (
|
||||||
const multiValueLabel = useMultiValueLabelStyle({ colorMode });
|
<SelectContext.Provider value={{ colorMode, isOpen, isError }}>
|
||||||
const multiValueRemove = useMultiValueRemoveStyle({ colorMode });
|
<ReactSelect<Opt, IsMulti>
|
||||||
const menuPortal = useMenuPortal();
|
onChange={defaultOnChange}
|
||||||
const rsTheme = useRSTheme();
|
onMenuClose={onClose}
|
||||||
|
onMenuOpen={onOpen}
|
||||||
return (
|
isClearable={true}
|
||||||
<SelectContext.Provider value={selectContext}>
|
options={options}
|
||||||
<ReactSelectChakra
|
isMulti={isMulti}
|
||||||
onChange={defaultOnChange}
|
theme={rsTheme}
|
||||||
onMenuClose={onClose}
|
components={{ Option, ...components }}
|
||||||
onMenuOpen={onOpen}
|
ref={ref}
|
||||||
isClearable={true}
|
styles={{
|
||||||
options={options}
|
menu,
|
||||||
isMulti={multi}
|
option,
|
||||||
theme={rsTheme}
|
control,
|
||||||
components={{ Option, ...components }}
|
menuList,
|
||||||
styles={{
|
menuPortal,
|
||||||
menuPortal,
|
multiValue,
|
||||||
multiValue,
|
singleValue,
|
||||||
multiValueLabel,
|
placeholder,
|
||||||
multiValueRemove,
|
multiValueLabel,
|
||||||
menu: useMenuStyle,
|
multiValueRemove,
|
||||||
option: useOptionStyle,
|
indicatorSeparator,
|
||||||
control: useControlStyle,
|
}}
|
||||||
menuList: useMenuListStyle,
|
{...rest}
|
||||||
singleValue: useSingleValueStyle,
|
/>
|
||||||
placeholder: usePlaceholderStyle,
|
</SelectContext.Provider>
|
||||||
indicatorSeparator: useIndicatorSeparatorStyle,
|
);
|
||||||
}}
|
},
|
||||||
{...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 */
|
import * as ReactSelect from 'react-select';
|
||||||
/* eslint @typescript-eslint/explicit-module-boundary-types: 0 */
|
|
||||||
|
|
||||||
import type {
|
import type { StylesProps, StylesConfigFunction } from 'react-select/dist/declarations/src/styles';
|
||||||
Props as IReactSelect,
|
import type { Theme, SingleOption } from '~/types';
|
||||||
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';
|
|
||||||
|
|
||||||
export interface TSelectState {
|
export type SelectOnChange<
|
||||||
[k: string]: string[];
|
Opt extends SingleOption = SingleOption,
|
||||||
}
|
IsMulti extends boolean = boolean,
|
||||||
|
> = NonNullable<ReactSelect.Props<Opt, IsMulti>['onChange']>;
|
||||||
|
|
||||||
export type TOptions = Array<SingleOption | OptionGroup>;
|
export interface TSelectBase<Opt extends SingleOption, IsMulti extends boolean>
|
||||||
|
extends ReactSelect.Props<Opt, IsMulti> {
|
||||||
export type TReactSelectChakra = Omit<IReactSelect, 'isMulti' | 'onSelect' | 'onChange'> &
|
|
||||||
Omit<BoxProps, 'onChange' | 'onSelect'>;
|
|
||||||
|
|
||||||
export interface TSelectBase extends TReactSelectChakra {
|
|
||||||
name: string;
|
name: string;
|
||||||
multi?: boolean;
|
isMulti?: IsMulti;
|
||||||
isError?: boolean;
|
isError?: boolean;
|
||||||
options: TOptions;
|
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
onSelect?: (s: SingleOption[]) => void;
|
onSelect?: (s: ReactSelect.MultiValue<Opt>) => void;
|
||||||
onChange?: (c: SingleOption | SingleOption[]) => void;
|
|
||||||
colorScheme?: Theme.ColorNames;
|
colorScheme?: Theme.ColorNames;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -42,40 +24,32 @@ export interface TSelectContext {
|
||||||
isError: boolean;
|
isError: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TMultiValueRemoveProps {
|
export interface RSStyleCallbackProps {
|
||||||
children: Node;
|
colorMode: 'light' | 'dark';
|
||||||
data: any;
|
|
||||||
innerProps: {
|
|
||||||
className: string;
|
|
||||||
onTouchEnd: (e: any) => void;
|
|
||||||
onClick: (e: any) => void;
|
|
||||||
onMouseDown: (e: any) => void;
|
|
||||||
};
|
|
||||||
selectProps: any;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TRSTheme extends Omit<RSTheme, 'borderRadius'> {
|
type StyleConfigKeys = keyof ReactSelect.StylesConfig<
|
||||||
borderRadius: string | number;
|
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 function isMultiValue<Opt extends SingleOption>(
|
||||||
|
value: ReactSelect.SingleValue<Opt> | ReactSelect.MultiValue<Opt>,
|
||||||
export type TMenu = MenuProps<TOptions, false>;
|
): value is NonNullable<ReactSelect.MultiValue<Opt>> {
|
||||||
|
return value !== null && Array.isArray(value);
|
||||||
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>;
|
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,9 @@ import plur from 'plur';
|
||||||
import isEqual from 'react-fast-compare';
|
import isEqual from 'react-fast-compare';
|
||||||
import { all, andJoin, dedupObjectArray, withDev } from '~/util';
|
import { all, andJoin, dedupObjectArray, withDev } from '~/util';
|
||||||
|
|
||||||
|
import type { SingleValue, MultiValue } from 'react-select';
|
||||||
import type { StateCreator } from 'zustand';
|
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 { SingleOption, Directive, FormData, Text } from '~/types';
|
||||||
import type { UseDevice } from './types';
|
import type { UseDevice } from './types';
|
||||||
|
|
||||||
|
|
@ -21,9 +22,9 @@ interface FormValues {
|
||||||
/**
|
/**
|
||||||
* Selected *options*, vs. values.
|
* Selected *options*, vs. values.
|
||||||
*/
|
*/
|
||||||
interface FormSelections {
|
interface FormSelections<Opt extends SingleOption = SingleOption> {
|
||||||
queryLocation: SingleOption[];
|
queryLocation: MultiValue<Opt>;
|
||||||
queryType: SingleOption | null;
|
queryType: SingleValue<Opt>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Filtered {
|
interface Filtered {
|
||||||
|
|
@ -39,13 +40,13 @@ interface Target {
|
||||||
display: string;
|
display: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FormStateType {
|
interface FormStateType<Opt extends SingleOption = SingleOption> {
|
||||||
// Values
|
// Values
|
||||||
filtered: Filtered;
|
filtered: Filtered;
|
||||||
form: FormValues;
|
form: FormValues;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
responses: Responses;
|
responses: Responses;
|
||||||
selections: FormSelections;
|
selections: FormSelections<Opt>;
|
||||||
status: FormStatus;
|
status: FormStatus;
|
||||||
target: Target;
|
target: Target;
|
||||||
resolvedIsOpen: boolean;
|
resolvedIsOpen: boolean;
|
||||||
|
|
@ -57,7 +58,13 @@ interface FormStateType {
|
||||||
addResponse(deviceId: string, data: QueryResponse): void;
|
addResponse(deviceId: string, data: QueryResponse): void;
|
||||||
setLoading(value: boolean): void;
|
setLoading(value: boolean): void;
|
||||||
setStatus(value: FormStatus): 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;
|
setTarget(update: Partial<Target>): void;
|
||||||
getDirective(): Directive | null;
|
getDirective(): Directive | null;
|
||||||
reset(): void;
|
reset(): void;
|
||||||
|
|
@ -95,7 +102,10 @@ const formState: StateCreator<FormStateType> = (set, get) => ({
|
||||||
set({ status });
|
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 } }));
|
set(state => ({ selections: { ...state.selections, [field]: value } }));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -223,6 +233,10 @@ export const useFormState = create<FormStateType>(
|
||||||
withDev<FormStateType>(formState, 'useFormState'),
|
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 {
|
export function useView(): FormStatus {
|
||||||
const { status, form } = useFormState(({ status, form }) => ({ status, form }));
|
const { status, form } = useFormState(({ status, form }) => ({ status, form }));
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,37 @@
|
||||||
import { useMemo } from 'react';
|
import { useMemo, useCallback } from 'react';
|
||||||
import { getColor, isLight } from '@chakra-ui/theme-tools';
|
import { getColor, isLight } from '@chakra-ui/theme-tools';
|
||||||
import { useTheme } from '~/context';
|
import { useTheme } from '~/context';
|
||||||
|
|
||||||
import type { TOpposingOptions } from './types';
|
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
|
* 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.
|
* opposing color should be black or white.
|
||||||
*/
|
*/
|
||||||
export function useIsDark(color: string): boolean {
|
export function useIsDark(color: string): boolean {
|
||||||
|
const isDarkFn = useIsDarkCallback();
|
||||||
|
return useMemo((): boolean => isDarkFn(color), [color, isDarkFn]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useIsDarkCallback(): UseIsDarkCallbackReturn {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
if (typeof color === 'string' && color.match(/[a-zA-Z]+\.[a-zA-Z0-9]+/g)) {
|
return useCallback(
|
||||||
color = getColor(theme, color, color);
|
(color: string): boolean => {
|
||||||
}
|
if (typeof color === 'string' && color.match(/[a-zA-Z]+\.[a-zA-Z0-9]+/g)) {
|
||||||
let opposingShouldBeDark = true;
|
color = getColor(theme, color, color);
|
||||||
try {
|
}
|
||||||
opposingShouldBeDark = isLight(color)(theme);
|
let opposingShouldBeDark = true;
|
||||||
} catch (err) {
|
try {
|
||||||
console.error(err);
|
opposingShouldBeDark = isLight(color)(theme);
|
||||||
}
|
} catch (err) {
|
||||||
return opposingShouldBeDark;
|
console.error(err);
|
||||||
|
}
|
||||||
|
return opposingShouldBeDark;
|
||||||
|
},
|
||||||
|
[theme],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -36,3 +48,17 @@ export function useOpposingColor(color: string, options?: TOpposingOptions): str
|
||||||
}
|
}
|
||||||
}, [isBlack, options?.dark, options?.light]);
|
}, [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.
|
// Set up the proxy.
|
||||||
if (dev && devProxy) {
|
if (dev && devProxy) {
|
||||||
Object.keys(devProxy).forEach(function (context) {
|
Object.keys(devProxy).forEach(context => {
|
||||||
server.use(proxyMiddleware(context, devProxy[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,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "eslint . --ext .ts --ext .tsx",
|
"lint": "eslint . --ext .ts --ext .tsx",
|
||||||
"dev": "node nextdev",
|
"dev": "export NODE_OPTIONS=--openssl-legacy-provider; node nextdev",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"format": "prettier --config ./.prettierrc -c -w .",
|
"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"
|
"test": "jest"
|
||||||
},
|
},
|
||||||
"browserslist": "> 0.25%, not dead",
|
"browserslist": "> 0.25%, not dead",
|
||||||
|
|
@ -27,6 +27,7 @@
|
||||||
"dayjs": "^1.10.4",
|
"dayjs": "^1.10.4",
|
||||||
"framer-motion": "^4.1.17",
|
"framer-motion": "^4.1.17",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
"merge-anything": "^4.0.1",
|
||||||
"next": "^11.1.2",
|
"next": "^11.1.2",
|
||||||
"palette-by-numbers": "^0.1.5",
|
"palette-by-numbers": "^0.1.5",
|
||||||
"plur": "^4.0.0",
|
"plur": "^4.0.0",
|
||||||
|
|
@ -40,7 +41,7 @@
|
||||||
"react-hook-form": "^7.7.0",
|
"react-hook-form": "^7.7.0",
|
||||||
"react-markdown": "^5.0.3",
|
"react-markdown": "^5.0.3",
|
||||||
"react-query": "^3.16.0",
|
"react-query": "^3.16.0",
|
||||||
"react-select": "^4.3.1",
|
"react-select": "^5.2.1",
|
||||||
"react-table": "^7.7.0",
|
"react-table": "^7.7.0",
|
||||||
"remark-gfm": "^1.0.0",
|
"remark-gfm": "^1.0.0",
|
||||||
"string-format": "^2.0.0",
|
"string-format": "^2.0.0",
|
||||||
|
|
@ -51,9 +52,9 @@
|
||||||
"@testing-library/jest-dom": "^5.14.1",
|
"@testing-library/jest-dom": "^5.14.1",
|
||||||
"@testing-library/react": "^12.1.0",
|
"@testing-library/react": "^12.1.0",
|
||||||
"@types/dagre": "^0.7.44",
|
"@types/dagre": "^0.7.44",
|
||||||
|
"@types/express": "^4.17.13",
|
||||||
"@types/node": "^14.14.41",
|
"@types/node": "^14.14.41",
|
||||||
"@types/react": "^17.0.3",
|
"@types/react": "^17.0.3",
|
||||||
"@types/react-select": "^4.0.15",
|
|
||||||
"@types/react-table": "^7.7.1",
|
"@types/react-table": "^7.7.1",
|
||||||
"@types/string-format": "^2.0.0",
|
"@types/string-format": "^2.0.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.31.0",
|
"@typescript-eslint/eslint-plugin": "^4.31.0",
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,19 @@
|
||||||
type AnyOption = {
|
interface AnyOption {
|
||||||
label: string;
|
label: string;
|
||||||
};
|
}
|
||||||
|
|
||||||
export type SingleOption = AnyOption & {
|
export interface SingleOption<T extends Record<string, unknown> = Record<string, unknown>>
|
||||||
|
extends AnyOption {
|
||||||
value: string;
|
value: string;
|
||||||
group?: string;
|
group?: string;
|
||||||
tags?: string[];
|
tags?: string[];
|
||||||
data?: Record<string, unknown>;
|
data?: T;
|
||||||
};
|
}
|
||||||
|
|
||||||
export type OptionGroup = AnyOption & {
|
export interface OptionGroup<Opt extends SingleOption> extends AnyOption {
|
||||||
options: SingleOption[];
|
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[] };
|
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:
|
dependencies:
|
||||||
"@babel/types" "^7.3.0"
|
"@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@*":
|
"@types/d3-array@*":
|
||||||
version "2.9.0"
|
version "2.9.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-2.9.0.tgz#fb6c3d7d7640259e68771cd90cc5db5ac1a1a012"
|
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"
|
resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d"
|
||||||
integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==
|
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@*":
|
"@types/geojson@*":
|
||||||
version "7946.0.7"
|
version "7946.0.7"
|
||||||
resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.7.tgz#c8fa532b60a0042219cdf173ca21a975ef0666ad"
|
resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.7.tgz#c8fa532b60a0042219cdf173ca21a975ef0666ad"
|
||||||
|
|
@ -1972,6 +2006,11 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/unist" "*"
|
"@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":
|
"@types/node@*", "@types/node@^14.14.41":
|
||||||
version "14.14.41"
|
version "14.14.41"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.41.tgz#d0b939d94c1d7bd53d04824af45f1139b8c45615"
|
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"
|
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
|
||||||
integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==
|
integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==
|
||||||
|
|
||||||
"@types/react-dom@*":
|
"@types/qs@*":
|
||||||
version "16.9.8"
|
version "6.9.7"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.8.tgz#fe4c1e11dfc67155733dfa6aa65108b4971cb423"
|
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb"
|
||||||
integrity sha512-ykkPQ+5nFknnlU6lDd947WbQ6TE3NNzbQAkInC2EKY1qeYdTKp7onFusmYZb+ityzx2YviqT6BXSu+LyWWJwcA==
|
integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==
|
||||||
dependencies:
|
|
||||||
"@types/react" "*"
|
"@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":
|
"@types/react-redux@^7.1.16":
|
||||||
version "7.1.16"
|
version "7.1.16"
|
||||||
|
|
@ -2009,16 +2051,6 @@
|
||||||
hoist-non-react-statics "^3.3.0"
|
hoist-non-react-statics "^3.3.0"
|
||||||
redux "^4.0.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":
|
"@types/react-table@^7.7.1":
|
||||||
version "7.7.1"
|
version "7.7.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react-table/-/react-table-7.7.1.tgz#cac73133fc185e152e31435f8e6fce89ab868661"
|
resolved "https://registry.yarnpkg.com/@types/react-table/-/react-table-7.7.1.tgz#cac73133fc185e152e31435f8e6fce89ab868661"
|
||||||
|
|
@ -2026,10 +2058,10 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/react" "*"
|
"@types/react" "*"
|
||||||
|
|
||||||
"@types/react-transition-group@*":
|
"@types/react-transition-group@^4.4.0":
|
||||||
version "4.4.0"
|
version "4.4.4"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.0.tgz#882839db465df1320e4753e6e9f70ca7e9b4d46d"
|
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.4.tgz#acd4cceaa2be6b757db61ed7b432e103242d163e"
|
||||||
integrity sha512-/QfLHGpu+2fQOqQaXh8MG9q03bFENooTb/it4jr5kKaZlDQfWvjqWZg48AwzPVMBHlRuTRAY7hRHCEOXz5kV6w==
|
integrity sha512-7gAPz7anVK5xzbeQW9wFBDg7G++aPLAFY0QaSMOou9rJZpbuI58WAuJrgu+qR92l61grlnCUe7AFX8KGahAgug==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/react" "*"
|
"@types/react" "*"
|
||||||
|
|
||||||
|
|
@ -2055,6 +2087,14 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.1.tgz#18845205e86ff0038517aab7a18a62a6b9f71275"
|
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.1.tgz#18845205e86ff0038517aab7a18a62a6b9f71275"
|
||||||
integrity sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA==
|
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":
|
"@types/stack-utils@^2.0.0":
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c"
|
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"
|
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
|
||||||
integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
|
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:
|
isarray@^1.0.0, isarray@~1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
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"
|
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.1.1.tgz#047b6e3199b508eaec03504de71229b8eb1d75c0"
|
||||||
integrity sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA==
|
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:
|
merge-descriptors@1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
|
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"
|
kleur "^3.0.3"
|
||||||
sisteransi "^1.0.5"
|
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"
|
version "15.7.2"
|
||||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
|
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
|
||||||
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
|
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"
|
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.7.0.tgz#11072091fde39775ad834321d9f18f160d47e997"
|
||||||
integrity sha512-WhTl6lbQrV942yzmDL+Eq9AGwG0gARHBH198wuxYIoxtvrsBt5EskdTcRjAYXvJv9N5ojd3t+QoT4QXgDi5l0g==
|
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:
|
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"
|
version "17.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
|
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-callback-ref "^1.2.3"
|
||||||
use-sidecar "^1.0.1"
|
use-sidecar "^1.0.1"
|
||||||
|
|
||||||
react-select@^4.3.1:
|
react-select@^5.2.1:
|
||||||
version "4.3.1"
|
version "5.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-select/-/react-select-4.3.1.tgz#389fc07c9bc7cf7d3c377b7a05ea18cd7399cb81"
|
resolved "https://registry.yarnpkg.com/react-select/-/react-select-5.2.1.tgz#416c25c6b79b94687702374e019c4f2ed9d159d6"
|
||||||
integrity sha512-HBBd0dYwkF5aZk1zP81Wx5UsLIIT2lSvAY2JiJo199LjoLHoivjn9//KsmvQMEFGNhe58xyuOITjfxKCcGc62Q==
|
integrity sha512-OOyNzfKrhOcw/BlembyGWgdlJ2ObZRaqmQppPFut1RptJO423j+Y+JIsmxkvsZ4D/3CpOmwIlCvWbbAWEdh12A==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.12.0"
|
"@babel/runtime" "^7.12.0"
|
||||||
"@emotion/cache" "^11.4.0"
|
"@emotion/cache" "^11.4.0"
|
||||||
"@emotion/react" "^11.1.1"
|
"@emotion/react" "^11.1.1"
|
||||||
|
"@types/react-transition-group" "^4.4.0"
|
||||||
memoize-one "^5.0.0"
|
memoize-one "^5.0.0"
|
||||||
prop-types "^15.6.0"
|
prop-types "^15.6.0"
|
||||||
react-input-autosize "^3.0.0"
|
|
||||||
react-transition-group "^4.3.0"
|
react-transition-group "^4.3.0"
|
||||||
|
|
||||||
react-shallow-renderer@^16.13.1:
|
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"
|
resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92"
|
||||||
integrity sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==
|
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:
|
tsconfig-paths@^3.11.0:
|
||||||
version "3.11.0"
|
version "3.11.0"
|
||||||
resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.11.0.tgz#954c1fe973da6339c78e06b03ce2e48810b65f36"
|
resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.11.0.tgz#954c1fe973da6339c78e06b03ce2e48810b65f36"
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue