Skip to content

Commit

Permalink
feat: final
Browse files Browse the repository at this point in the history
  • Loading branch information
danielsimao committed May 6, 2024
1 parent f6bdbd3 commit 17e7ddd
Show file tree
Hide file tree
Showing 8 changed files with 230 additions and 90 deletions.
29 changes: 5 additions & 24 deletions packages/components/src/TokenInput/BaseTokenInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ 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 { trimDecimals } from '../utils';
import { HelperTextProps } from '../HelperText';
import { LabelProps } from '../Label';
import { Field, FieldProps, useFieldProps } from '../Field';
Expand Down Expand Up @@ -76,7 +75,7 @@ const BaseTokenInput = forwardRef<HTMLInputElement, BaseTokenInputProps>(
size = 'md',
defaultValue,
inputMode,
value: valueProp,
value,
endAdornment,
currency,
onChange,
Expand All @@ -85,36 +84,19 @@ const BaseTokenInput = forwardRef<HTMLInputElement, BaseTokenInputProps>(
},
ref
): JSX.Element => {
const [value, setValue] = useState<string | undefined>(defaultValue?.toString());
const inputRef = useDOMRef(ref);

const format = useCurrencyFormatter();

// Observes currency field and correct decimals places if needed
useEffect(() => {
if (value && currency) {
const trimmedValue = trimDecimals(value, currency.decimals);

if (value !== trimmedValue) {
setValue(trimmedValue);
onValueChange?.(trimmedValue);
}
}
}, [currency]);

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

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

const { inputProps, descriptionProps, errorMessageProps, labelProps } = useTextField(
{
...props,
label,
inputMode,
isInvalid: isInvalid || !!props.errorMessage,
value: value,
value,
onChange: () => {},
defaultValue,
placeholder,
autoComplete: 'off'
},
Expand All @@ -134,7 +116,6 @@ const BaseTokenInput = forwardRef<HTMLInputElement, BaseTokenInputProps>(
if (isEmpty || isValid) {
onChange?.(e);
onValueChange?.(value);
setValue(value);
}
},
[onChange, onValueChange, currency]
Expand Down
37 changes: 18 additions & 19 deletions packages/components/src/TokenInput/SelectableTokenInput.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { chain, useId } from '@react-aria/utils';
import { Key, ReactNode, forwardRef, useCallback, useEffect, useState } from 'react';
import { Key, ReactNode, forwardRef, useCallback, useEffect } from 'react';

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

Expand All @@ -11,8 +11,11 @@ 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,35 +34,30 @@ const SelectableTokenInput = forwardRef<HTMLInputElement, SelectableTokenInputPr
isDisabled,
balanceLabel,
humanBalance,
currency,
items,
onClickBalance,
onChangeCurrency,
size,
...props
},
ref
): JSX.Element => {
const selectHelperTextId = useId();

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

const [currency, setCurrency] = useState<any | undefined>(defaultCurrency);

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

if (value === undefined) return;
if (selectProps?.value === undefined) return;

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

setCurrency(tokenData?.currency);
}, [selectProps?.value]);
onChangeCurrency?.(item?.currency);
}, [selectProps?.value, onChangeCurrency]);

const handleTokenChange = useCallback(
const handleSelectionChange = useCallback(
(ticker: Key) => {
const tokenData = (selectProps.items as TokenData[]).find((item) => item.currency.symbol === ticker);
const tokenData = (items as TokenData[]).find((item) => item.currency.symbol === ticker);

setCurrency(tokenData?.currency);
onChangeCurrency?.(tokenData?.currency);
},
[selectProps]
);
Expand All @@ -71,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 @@ -80,9 +78,10 @@ const SelectableTokenInput = forwardRef<HTMLInputElement, SelectableTokenInputPr
description={undefined}
errorMessage={undefined}
isInvalid={isInvalid}
items={items}
size={size}
value={currency?.symbol}
onSelectionChange={chain(onSelectionChange, handleTokenChange)}
onSelectionChange={chain(onSelectionChange, handleSelectionChange)}
/>
);

Expand Down
49 changes: 42 additions & 7 deletions packages/components/src/TokenInput/TokenInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,28 @@ import { useDOMRef } from '@interlay/hooks';
import { mergeProps, useId } from '@react-aria/utils';
import { ChangeEvent, FocusEvent, forwardRef, useCallback, useEffect, useState } from 'react';

import { trimDecimals } from '../utils';

import { FixedTokenInput, FixedTokenInputProps } from './FixedTokenInput';
import { SelectableTokenInput, SelectableTokenInputProps } from './SelectableTokenInput';

const getDefaultCurrency = (props: TokenInputProps) => {
switch (props.type) {
default:
case 'fixed':
return (props as FixedTokenInputProps).currency;
case 'selectable':
return (props.items || []).find((item) => item.currency.symbol === props.selectProps?.defaultValue);
}
};

type Props = {
onValueChange?: (value?: string | number) => void;
// TODO: define type when repo moved to bob-ui
};

type FixedAttrs = Omit<FixedTokenInputProps, keyof Props>;

type SelectableAttrs = Omit<SelectableTokenInputProps, keyof Props>;
type SelectableAttrs = Omit<SelectableTokenInputProps, keyof Props | 'currency'>;

type InheritAttrs = ({ type?: 'fixed' } & FixedAttrs) | ({ type?: 'selectable' } & SelectableAttrs);

Expand All @@ -24,6 +35,7 @@ const TokenInput = forwardRef<HTMLInputElement, TokenInputProps>((props, ref): J
const inputRef = useDOMRef<HTMLInputElement>(ref);

const [value, setValue] = useState(defaultValue);
const [currency, setCurrency] = useState<any | undefined>(getDefaultCurrency(props));

const inputId = useId();

Expand All @@ -33,6 +45,17 @@ const TokenInput = forwardRef<HTMLInputElement, TokenInputProps>((props, ref): J
setValue(valueProp);
}, [valueProp]);

useEffect(() => {
if (value && currency) {
const trimmedValue = trimDecimals(value, currency.decimals);

if (value !== trimmedValue) {
setValue(trimmedValue);
onValueChange?.(trimmedValue);
}
}
}, [currency]);

const handleChange = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
Expand All @@ -45,12 +68,16 @@ const TokenInput = forwardRef<HTMLInputElement, TokenInputProps>((props, ref): J

const handleClickBalance = useCallback(
(balance: string) => {
if (!currency) return;

inputRef.current?.focus();

setValue(balance);
onValueChange?.(balance);
const value = trimDecimals(balance, currency.decimals);

setValue(value);
onValueChange?.(value);
},
[onValueChange, inputRef.current]
[onValueChange, inputRef.current, currency]
);

const handleBlur = useCallback(
Expand All @@ -70,6 +97,8 @@ const TokenInput = forwardRef<HTMLInputElement, TokenInputProps>((props, ref): J
[onBlur]
);

const handleChangeCurrency = useCallback((currency: any) => setCurrency(currency), []);

const numberInputProps: Partial<TokenInputProps> =
balance !== undefined
? {
Expand All @@ -88,10 +117,16 @@ const TokenInput = forwardRef<HTMLInputElement, TokenInputProps>((props, ref): J
};

if (props.type === 'selectable') {
return <SelectableTokenInput {...mergeProps(otherProps, commonProps)} />;
return (
<SelectableTokenInput
{...mergeProps(otherProps, commonProps)}
currency={currency}
onChangeCurrency={handleChangeCurrency}
/>
);
}

return <FixedTokenInput {...mergeProps(otherProps, commonProps)} />;
return <FixedTokenInput {...mergeProps(otherProps, commonProps)} currency={currency} />;
});

TokenInput.displayName = 'TokenInput';
Expand Down
Loading

0 comments on commit 17e7ddd

Please sign in to comment.