Skip to content

Commit

Permalink
feat: improve TokenInput and use-form (#93)
Browse files Browse the repository at this point in the history
  • Loading branch information
danielsimao authored May 6, 2024
1 parent 619b6ca commit b488881
Show file tree
Hide file tree
Showing 15 changed files with 400 additions and 182 deletions.
6 changes: 6 additions & 0 deletions .changeset/six-weeks-hammer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@interlay/ui": patch
"@interlay/hooks": patch
---

Fix/token input decimal undefined
46 changes: 20 additions & 26 deletions packages/components/src/TokenInput/BaseTokenInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useCurrencyFormatter, useDOMRef } from '@interlay/hooks';
import { Spacing, TokenInputSize } from '@interlay/theme';
import { AriaTextFieldOptions, useTextField } from '@react-aria/textfield';
import { mergeProps } from '@react-aria/utils';
import { ChangeEventHandler, FocusEvent, forwardRef, ReactNode, useCallback, useEffect, useState } from 'react';
import { ChangeEventHandler, FocusEvent, forwardRef, ReactNode, useCallback } from 'react';

import { HelperTextProps } from '../HelperText';
import { LabelProps } from '../Label';
Expand Down Expand Up @@ -42,7 +42,7 @@ type Props = {
value?: string;
defaultValue?: string;
// TODO: use Currency from bob-ui
currency: { decimals: number };
currency?: { decimals: number };
onValueChange?: (value: string | number) => void;
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
onFocus?: (e: FocusEvent<Element>) => void;
Expand Down Expand Up @@ -75,7 +75,7 @@ const BaseTokenInput = forwardRef<HTMLInputElement, BaseTokenInputProps>(
size = 'md',
defaultValue,
inputMode,
value: valueProp,
value,
endAdornment,
currency,
onChange,
Expand All @@ -84,49 +84,43 @@ const BaseTokenInput = forwardRef<HTMLInputElement, BaseTokenInputProps>(
},
ref
): JSX.Element => {
const [value, setValue] = useState<string | undefined>(defaultValue?.toString());
const inputRef = useDOMRef(ref);

const format = useCurrencyFormatter();

const { inputProps, descriptionProps, errorMessageProps, labelProps } = useTextField(
{
...props,
label,
inputMode,
isInvalid: isInvalid || !!props.errorMessage,
value,
onChange: () => {},
defaultValue,
placeholder,
autoComplete: 'off'
},
inputRef
);

const handleChange: ChangeEventHandler<HTMLInputElement> = useCallback(
(e) => {
const value = e.target.value;

const isEmpty = value === '';
const hasValidDecimalFormat = RegExp(`^\\d*(?:\\\\[.])?\\d*$`).test(escapeRegExp(value));
const hasValidDecimalsAmount = hasCorrectDecimals(value, currency.decimals);
const hasValidDecimalsAmount = currency ? hasCorrectDecimals(value, currency.decimals) : true;

const isValid = hasValidDecimalFormat && hasValidDecimalsAmount;

if (isEmpty || isValid) {
onChange?.(e);
onValueChange?.(value);
setValue(value);
}
},
[onChange, onValueChange]
);

const { inputProps, descriptionProps, errorMessageProps, labelProps } = useTextField(
{
...props,
label,
inputMode,
isInvalid: isInvalid || !!props.errorMessage,
value: value,
placeholder,
autoComplete: 'off'
},
inputRef
[onChange, onValueChange, currency]
);

useEffect(() => {
if (valueProp === undefined) return;

setValue(valueProp.toString());
}, [valueProp]);

const hasLabel = !!label || !!balance;

// FIXME: move this into Field
Expand Down
6 changes: 3 additions & 3 deletions packages/components/src/TokenInput/FixedTokenInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import { BaseTokenInput, BaseTokenInputProps } from './BaseTokenInput';
import { TokenInputBalance } from './TokenInputBalance';

type Props = {
currency: any;
balance?: string;
humanBalance?: string | number;
balanceLabel?: ReactNode;
onClickBalance?: (balance: string | number) => void;
ticker: string;
logoUrl: string;
};

Expand All @@ -24,11 +24,11 @@ const FixedTokenInput = forwardRef<HTMLInputElement, FixedTokenInputProps>(
humanBalance,
balanceLabel,
onClickBalance,
ticker: tickerProp,
logoUrl,
isDisabled,
id,
size = 'md',
currency,
...props
},
ref
Expand All @@ -49,7 +49,7 @@ const FixedTokenInput = forwardRef<HTMLInputElement, FixedTokenInputProps>(
{...props}
ref={ref}
balance={balance}
endAdornment={<TokenAdornment logoUrl={logoUrl} size={size} ticker={tickerProp} />}
endAdornment={<TokenAdornment logoUrl={logoUrl} size={size} ticker={currency.symbol} />}
id={id}
isDisabled={isDisabled}
size={size}
Expand Down
43 changes: 28 additions & 15 deletions packages/components/src/TokenInput/SelectableTokenInput.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import { chain, useId } from '@react-aria/utils';
import { Key, ReactNode, forwardRef, useEffect, useState } from 'react';
import { Key, ReactNode, forwardRef, useCallback, useEffect } from 'react';

import { HelperText } from '../HelperText';

import { BaseTokenInput, BaseTokenInputProps } from './BaseTokenInput';
import { TokenInputBalance } from './TokenInputBalance';
import { TokenSelect, TokenSelectProps } from './TokenSelect';
import { TokenData, TokenSelect, TokenSelectProps } from './TokenSelect';

type Props = {
balance?: string;
humanBalance?: string | number;
balanceLabel?: ReactNode;
currency?: any;
items?: TokenData[];
onClickBalance?: (balance: string) => void;
selectProps: Omit<TokenSelectProps, 'label' | 'helperTextId'>;
onChangeCurrency?: (currency?: any) => void;
selectProps?: Omit<TokenSelectProps, 'label' | 'helperTextId' | 'items'>;
};

type InheritAttrs = Omit<BaseTokenInputProps, keyof Props | 'endAdornment'>;
Expand All @@ -31,25 +34,33 @@ const SelectableTokenInput = forwardRef<HTMLInputElement, SelectableTokenInputPr
isDisabled,
balanceLabel,
humanBalance,
currency,
items,
onClickBalance,
onChangeCurrency,
size,
...props
},
ref
): JSX.Element => {
const selectHelperTextId = useId();

const [ticker, setTicker] = useState<string | undefined>(selectProps?.defaultValue?.toString());

useEffect(() => {
const value = selectProps?.value;
if (selectProps?.value === undefined) return;

const item = (items as TokenData[]).find((item) => item.currency.symbol === selectProps?.value);

if (value === undefined) return;
onChangeCurrency?.(item?.currency);
}, [selectProps?.value, onChangeCurrency]);

setTicker(value.toString());
}, [selectProps?.value]);
const handleSelectionChange = useCallback(
(ticker: Key) => {
const tokenData = (items as TokenData[]).find((item) => item.currency.symbol === ticker);

const handleTokenChange = (ticker: Key) => setTicker(ticker as string);
onChangeCurrency?.(tokenData?.currency);
},
[selectProps]
);

// Prioritise Number Input description and error message
const hasNumberFieldMessages = !!(errorMessage || description);
Expand All @@ -58,7 +69,7 @@ const SelectableTokenInput = forwardRef<HTMLInputElement, SelectableTokenInputPr

const isInvalid = !hasNumberFieldMessages && !!selectProps?.errorMessage;

const { onSelectionChange, ...restSelectProps } = selectProps;
const { onSelectionChange, ...restSelectProps } = selectProps || {};

const endAdornment = (
<TokenSelect
Expand All @@ -67,18 +78,19 @@ const SelectableTokenInput = forwardRef<HTMLInputElement, SelectableTokenInputPr
description={undefined}
errorMessage={undefined}
isInvalid={isInvalid}
items={items}
size={size}
value={ticker}
onSelectionChange={chain(onSelectionChange, handleTokenChange)}
value={currency?.symbol}
onSelectionChange={chain(onSelectionChange, handleSelectionChange)}
/>
);

const balance = balanceProp !== undefined && (
<TokenInputBalance
balance={ticker ? balanceProp : '0'}
balance={currency ? balanceProp : '0'}
balanceHuman={humanBalance}
inputId={id}
isDisabled={isDisabled || !ticker}
isDisabled={isDisabled || !currency}
label={balanceLabel}
onClickBalance={onClickBalance}
/>
Expand All @@ -88,6 +100,7 @@ const SelectableTokenInput = forwardRef<HTMLInputElement, SelectableTokenInputPr
<BaseTokenInput
ref={ref}
balance={balance}
currency={currency}
description={description}
endAdornment={endAdornment}
errorMessage={errorMessage}
Expand Down
Loading

0 comments on commit b488881

Please sign in to comment.