diff --git a/packages/ui/src/components/library/Autocomplete.tsx b/packages/ui/src/components/library/Autocomplete.tsx index 0bf68bf2..0f9761d5 100644 --- a/packages/ui/src/components/library/Autocomplete.tsx +++ b/packages/ui/src/components/library/Autocomplete.tsx @@ -1,37 +1,57 @@ import { Autocomplete as AutocompleteMui } from '@mui/material' -import React, { SyntheticEvent } from 'react' +import React from 'react' import { styled } from '@mui/material/styles' -import { AutocompleteRenderInputParams } from '@mui/material/Autocomplete/Autocomplete' +import { + AutocompleteRenderInputParams, + AutocompleteRenderOptionState +} from '@mui/material/Autocomplete/Autocomplete' import { HiOutlineChevronDown } from 'react-icons/hi2' -import { InjectedAccountWithMeta } from '@polkadot/extension-inject/types' - -interface AutocompleteProps { +import { + AutocompleteFreeSoloValueMapping, + AutocompleteInputChangeReason, + AutocompleteValue, + FilterOptionsState +} from '@mui/base/useAutocomplete/useAutocomplete' + +interface AutocompleteProps { InputProps?: Partial className?: string - isOptionEqualToValue?: (option: any, value: any) => boolean - filterOptions?: (options: any[], state: any) => any[] - options: any[] - getOptionLabel: (option: any) => string - onChange?: (event: any, value: any) => void - value?: any - renderOption: (props: any, option: any, state: any) => React.ReactNode + isOptionEqualToValue?: (option: T, value: T) => boolean + filterOptions?: (options: T[], state: FilterOptionsState) => T[] + options: ReadonlyArray + getOptionLabel?: (option: T | AutocompleteFreeSoloValueMapping) => string + onChange?: ( + event: React.SyntheticEvent, + value: AutocompleteValue + ) => void + value?: AutocompleteValue + renderOption?: ( + props: React.HTMLAttributes, + option: T, + state: AutocompleteRenderOptionState + ) => React.ReactNode renderInput: (params: AutocompleteRenderInputParams) => React.ReactNode - - disableClearable?: boolean - onKeyDown?: (e: any) => void + disableClearable?: DisableClearable + onKeyDown?: (e: React.SyntheticEvent) => void onInputChange?: ( - _: SyntheticEvent, - val: string | InjectedAccountWithMeta | null + event: React.SyntheticEvent, + value: string, + reason: AutocompleteInputChangeReason ) => void disabled?: boolean - freeSolo?: boolean + freeSolo?: FreeSolo iconSize?: string selectOnFocus?: boolean clearOnBlur?: boolean handleHomeEndKeys?: boolean } -const Autocomplete = ({ +const Autocomplete = < + T, + Multiple extends boolean | undefined, + DisableClearable extends boolean | undefined, + FreeSolo extends boolean | undefined +>({ className, isOptionEqualToValue, filterOptions, @@ -49,7 +69,7 @@ const Autocomplete = ({ clearOnBlur, handleHomeEndKeys, iconSize -}: AutocompleteProps) => { +}: AutocompleteProps) => { return ( ` .MuiAutocomplete-listbox { padding: 0; border: 1px solid ${({ theme }) => theme.custom.text.borderColor}; - border-radius: 0.5rem; + border-radius: 0.75rem; box-shadow: none; } diff --git a/packages/ui/src/components/library/InputField.tsx b/packages/ui/src/components/library/InputField.tsx index ae02ac6e..1b042f2c 100644 --- a/packages/ui/src/components/library/InputField.tsx +++ b/packages/ui/src/components/library/InputField.tsx @@ -51,7 +51,7 @@ export const InputStyledBaseCss = css` padding: 0.5rem 1.25rem; border: none; outline: 1.5px solid ${theme.custom.text.borderColor}; - border-radius: 0.5rem; + border-radius: 0.75rem; font-size: 1rem; font-family: 'Jost', sans-serif; @@ -63,7 +63,7 @@ export const InputStyledBaseCss = css` cursor: not-allowed; background: #f3f6f9; outline: 1.5px solid #e7e7e7; - border-radius: 0.5rem; + border-radius: 0.75rem; } ` diff --git a/packages/ui/src/components/modals/Send.tsx b/packages/ui/src/components/modals/Send.tsx index 30b1e91c..8bb4af58 100644 --- a/packages/ui/src/components/modals/Send.tsx +++ b/packages/ui/src/components/modals/Send.tsx @@ -274,7 +274,7 @@ const Send = ({ onClose, className, onSuccess, onFinalized }: Props) => { Send tx option.address + option.meta.name }) +const isInjectedAccountWithMeta = (value: any): value is InjectedAccountWithMeta => + value && value.address && value.meta && value.meta.source + const getOptionLabel = (option: string | InjectedAccountWithMeta | null) => { if (!option) return '' @@ -77,10 +83,19 @@ const AccountSelection = ({ }, [accountNames, selected]) const onChangeAutocomplete = useCallback( - (_: SyntheticEvent, val: string | InjectedAccountWithMeta | null) => { + ( + _: SyntheticEvent, + val: NonNullable< + | InjectedAccountWithMeta + | string + | undefined + | (string | InjectedAccountWithMeta | undefined)[] + > + ) => { setErrorMessage('') setName('') - const value = getOptionLabel(val) + + const value = getOptionLabel(val as string) setSelected(value) }, [] @@ -125,12 +140,8 @@ const AccountSelection = ({ const renderOption = ( props: React.HTMLAttributes, - option: { - address: string - meta: { - name: string - } - } + option: InjectedAccountWithMeta, + _: AutocompleteRenderOptionState ) => ( { + return value && value.address +} + const GenericAccountSelection = ({ className, accountList = [], @@ -102,13 +106,18 @@ const GenericAccountSelection = ({ }, []) const onChangeAutocomplete = useCallback( - (_: React.SyntheticEvent, val: AccountBaseInfo | string) => { + ( + _: React.SyntheticEvent, + val: NonNullable< + AccountBaseInfo | string | undefined | (string | AccountBaseInfo | undefined)[] + > + ) => { if (typeof val === 'string') { onChange({ address: val }) } else { - onChange(val) + isAccountBaseInfo(val) && onChange(val) } onInputBlur() }, diff --git a/packages/ui/src/components/selectors/MultiProxySelection.tsx b/packages/ui/src/components/selectors/MultiProxySelection.tsx index 5646e1e4..2722e79a 100644 --- a/packages/ui/src/components/selectors/MultiProxySelection.tsx +++ b/packages/ui/src/components/selectors/MultiProxySelection.tsx @@ -11,6 +11,9 @@ import { Autocomplete } from '../library' import { AutocompleteRenderInputParams } from '@mui/material/Autocomplete/Autocomplete' import TextFieldLargeStyled from '../library/TextFieldLargeStyled' +const isMultiProxy = (value: any): value is MultiProxy => + value && value.multisigs && value.multisigs.length > 0 + interface Props { className?: string } @@ -18,7 +21,7 @@ interface Props { const getDisplayAddress = (option?: MultiProxy) => option?.proxy ? option?.proxy : option?.multisigs[0].address -const isOptionEqualToValue = (option: MultiProxy | undefined, value: MultiProxy | undefined) => { +const isOptionEqualToValue = (option?: MultiProxy, value?: MultiProxy) => { if (!option || !value) return false if (!!option.proxy || !!value.proxy) { @@ -30,7 +33,6 @@ const isOptionEqualToValue = (option: MultiProxy | undefined, value: MultiProxy const MultiProxySelection = ({ className }: Props) => { const ref = useRef(null) const { multiProxyList, selectedMultiProxy, selectMultiProxy } = useMultiProxy() - const isSelectedProxy = useMemo(() => !!selectedMultiProxy?.proxy, [selectedMultiProxy]) // We only support one multisigs if they have no proxy const addressToShow = useMemo( @@ -47,31 +49,42 @@ const MultiProxySelection = ({ className }: Props) => { }) const getOptionLabel = useCallback( - (option: typeof selectedMultiProxy) => { + (option?: NonNullable): string => { // We only support one multisigs if they have no proxy - const addressToSearch = option?.proxy || option?.multisigs[0].address - const name = !!addressToSearch && accountNames[addressToSearch] - return name || (addressToSearch as string) + + if (isMultiProxy(option)) { + const addressToSearch = option?.proxy || option?.multisigs[0].address + const name = !!addressToSearch && accountNames[addressToSearch] + return name || (addressToSearch as string) + } + + return '' }, [accountNames] ) const onChange = useCallback( - (_: React.SyntheticEvent, val: typeof selectedMultiProxy) => { - if (!val) return + ( + _: React.SyntheticEvent, + value: NonNullable + ) => { + if (!value) return - selectMultiProxy(val) + isMultiProxy(value) && selectMultiProxy(value) }, [selectMultiProxy] ) - const handleSpecialKeys = useCallback((e: any) => { + const handleSpecialKeys = useCallback((e: React.KeyboardEvent) => { if (['Enter', 'Escape'].includes(e.key)) { ref?.current?.blur() } }, []) - const renderOptions = (props: React.HTMLAttributes, option: MultiProxy) => { + const renderOptions = ( + props: React.HTMLAttributes, + option: typeof selectedMultiProxy + ): React.ReactNode => { const displayAddress = getDisplayAddress(option) return ( diff --git a/packages/ui/src/components/selectors/SignerSelection.tsx b/packages/ui/src/components/selectors/SignerSelection.tsx index 3ea1c14d..412888bf 100644 --- a/packages/ui/src/components/selectors/SignerSelection.tsx +++ b/packages/ui/src/components/selectors/SignerSelection.tsx @@ -1,4 +1,5 @@ import { InputAdornment } from '@mui/material' +import * as React from 'react' import { useCallback, useEffect, useMemo } from 'react' import { styled } from '@mui/material/styles' import { createFilterOptions } from '@mui/material/Autocomplete' @@ -8,19 +9,22 @@ import AccountDisplay from '../AccountDisplay' import MultixIdenticon from '../MultixIdenticon' import { Autocomplete, TextFieldStyled } from '../library' import OptionMenuItem from './OptionMenuItem' -import * as React from 'react' import { AutocompleteRenderInputParams } from '@mui/material/Autocomplete/Autocomplete' +const isInjectedAccountWithMeta = (value: any): value is InjectedAccountWithMeta => { + return value && value.address && value.meta && value.meta.name +} + interface Props { className?: string possibleSigners: string[] onChange?: () => void } -const getOptionLabel = (option: InjectedAccountWithMeta | undefined) => { - if (!option) return '' +const getOptionLabel = (option?: NonNullable): string => { + if (!option || !isInjectedAccountWithMeta(option)) return '' - return option.meta.name || '' + return option.meta.name as string } const isOptionEqualToValue = ( @@ -54,8 +58,16 @@ const SignerSelection = ({ className, possibleSigners, onChange }: Props) => { }) const onChangeSigner = useCallback( - (_: any, newSelected: (typeof signersList)[0]) => { - newSelected && selectAccount(newSelected) + ( + _: React.SyntheticEvent, + newSelected: NonNullable< + | (typeof signersList)[0] + | string + | undefined + | (string | (typeof signersList)[0] | undefined)[] + > + ) => { + isInjectedAccountWithMeta(newSelected) && selectAccount(newSelected) onChange && onChange() }, [onChange, selectAccount] @@ -63,15 +75,19 @@ const SignerSelection = ({ className, possibleSigners, onChange }: Props) => { const renderOption = ( props: React.HTMLAttributes, - option: InjectedAccountWithMeta - ) => ( - - - - ) + option?: InjectedAccountWithMeta + ) => { + if (!option) return null + + return ( + + + + ) + } const renderInput = (params: AutocompleteRenderInputParams) => (