From c2bf29995c0424781d6d13518ad50314c172d291 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rui=20Sim=C3=A3o?= <rui.daniel.simao@gmail.com> Date: Wed, 14 Jun 2023 17:42:34 +0100 Subject: [PATCH 1/2] feat(TransactionDetails): extend component to support fee selector --- src/assets/locales/en/translation.json | 1 + .../Select/Select.stories.tsx | 2 +- src/component-library/Select/Select.tsx | 29 ++++++----- .../TokenInput/TokenListItem.tsx | 30 +++++++++++ .../TokenInput/TokenSelect.tsx | 29 ++--------- src/component-library/TokenInput/index.tsx | 2 + src/component-library/index.tsx | 4 +- .../TransactionDetails.style.tsx | 11 +++- .../TransactionDetails/TransactionDetails.tsx | 2 +- .../TransactionDetailsDt.tsx | 2 +- .../TransactionFeeSelect.tsx | 52 +++++++++++++++++++ src/components/TransactionDetails/index.tsx | 2 + .../SelectVaultCard/VaultSelect.tsx | 4 +- .../TransactionDetails/TransactionDetails.tsx | 42 +++++++++------ .../components/ChainSelect/ChainSelect.tsx | 4 +- 15 files changed, 154 insertions(+), 62 deletions(-) create mode 100644 src/component-library/TokenInput/TokenListItem.tsx create mode 100644 src/components/TransactionDetails/TransactionFeeSelect.tsx diff --git a/src/assets/locales/en/translation.json b/src/assets/locales/en/translation.json index b079a0e197..45f7524294 100644 --- a/src/assets/locales/en/translation.json +++ b/src/assets/locales/en/translation.json @@ -159,6 +159,7 @@ "dismiss": "Dismiss", "insufficient_token_balance": "Insufficient {{token}} balance", "amount": "Amount", + "select_token": "Select Token", "redeem_page": { "maximum_in_single_request": "Max redeemable in single request", "redeem": "Redeem", diff --git a/src/component-library/Select/Select.stories.tsx b/src/component-library/Select/Select.stories.tsx index f233b502d4..ebc724166b 100644 --- a/src/component-library/Select/Select.stories.tsx +++ b/src/component-library/Select/Select.stories.tsx @@ -6,7 +6,7 @@ import { Flex } from '../Flex'; import { Select, SelectProps } from './Select'; import { SelectTrigger, SelectTriggerProps } from './SelectTrigger'; -const SelectTemplate: Story<SelectProps<any>> = (args) => { +const SelectTemplate: Story<SelectProps<any, 'modal'>> = (args) => { return ( <Select {...args}> <Item textValue='BTC' key='BTC'> diff --git a/src/component-library/Select/Select.tsx b/src/component-library/Select/Select.tsx index faed655141..67991c2167 100644 --- a/src/component-library/Select/Select.tsx +++ b/src/component-library/Select/Select.tsx @@ -12,11 +12,13 @@ import { Sizes } from '../utils/prop-types'; import { SelectModal } from './SelectModal'; import { SelectTrigger } from './SelectTrigger'; +type SelectType = 'listbox' | 'modal'; + type SelectObject = Record<string, unknown>; // TODO: listbox to be implemented -type Props<T extends SelectObject> = { - type?: 'listbox' | 'modal'; +type Props<T extends SelectObject, F extends SelectType> = { + type?: F; open?: boolean; loading?: boolean; size?: Sizes; @@ -24,23 +26,26 @@ type Props<T extends SelectObject> = { asSelectTrigger?: any; renderValue?: (item: Node<T>) => ReactNode; placeholder?: ReactNode; - modalTitle?: ReactNode; + modalTitle?: F extends 'modal' ? ReactNode : never; }; -type InheritAttrs<T extends SelectObject = any> = Omit< +type InheritAttrs<F extends SelectType, T extends SelectObject = any> = Omit< CollectionBase<T> & FieldProps & AriaSelectProps<T>, - keyof Props<T> | 'isDisabled' | 'isLoading' | 'isOpen' | 'isRequired' | 'selectedKey' | 'defaultSelectedKey' + keyof Props<T, F> | 'isDisabled' | 'isLoading' | 'isOpen' | 'isRequired' | 'selectedKey' | 'defaultSelectedKey' >; -type NativeAttrs<T extends SelectObject> = Omit<React.InputHTMLAttributes<Element>, keyof Props<T>>; +type NativeAttrs<T extends SelectObject, F extends SelectType> = Omit< + React.InputHTMLAttributes<Element>, + keyof Props<T, F> +>; -type SelectProps<T extends SelectObject> = Props<T> & NativeAttrs<T> & InheritAttrs<T>; +type SelectProps<T extends SelectObject, F extends SelectType> = Props<T, F> & NativeAttrs<T, F> & InheritAttrs<F, T>; -const Select = <T extends SelectObject>( +const Select = <T extends SelectObject, F extends SelectType>( { value, defaultValue, - type = 'listbox', + type = 'listbox' as F, name, disabled, loading, @@ -57,7 +62,7 @@ const Select = <T extends SelectObject>( renderValue = (item) => item.rendered, items, ...props - }: SelectProps<T>, + }: SelectProps<T, F>, ref: ForwardedRef<HTMLInputElement> ): JSX.Element => { const inputRef = useDOMRef(ref); @@ -145,8 +150,8 @@ const Select = <T extends SelectObject>( ); }; -const _Select = forwardRef(Select) as <T extends SelectObject>( - props: SelectProps<T> & { ref?: React.ForwardedRef<HTMLInputElement> } +const _Select = forwardRef(Select) as <T extends SelectObject, F extends SelectType>( + props: SelectProps<T, F> & { ref?: React.ForwardedRef<HTMLInputElement> } ) => ReturnType<typeof Select>; Select.displayName = 'Select'; diff --git a/src/component-library/TokenInput/TokenListItem.tsx b/src/component-library/TokenInput/TokenListItem.tsx new file mode 100644 index 0000000000..a6de8a74e0 --- /dev/null +++ b/src/component-library/TokenInput/TokenListItem.tsx @@ -0,0 +1,30 @@ +import { CoinIcon } from '../CoinIcon'; +import { Flex } from '../Flex'; +import { useSelectModalContext } from '../Select/SelectModalContext'; +import { Span } from '../Text'; +import { StyledListItemLabel, StyledListTokenWrapper } from './TokenInput.style'; +import { TokenData } from './TokenSelect'; + +type TokenListItemProps = TokenData; + +const TokenListItem = ({ balance, balanceUSD, value, tickers }: TokenListItemProps): JSX.Element => { + const isSelected = useSelectModalContext().selectedItem?.key === value; + + return ( + <> + <StyledListTokenWrapper alignItems='center' gap='spacing2' flex='1'> + <CoinIcon size={tickers ? 'lg' : 'md'} ticker={value} tickers={tickers} /> + <StyledListItemLabel $isSelected={isSelected}>{value}</StyledListItemLabel> + </StyledListTokenWrapper> + <Flex direction='column' alignItems='flex-end' gap='spacing2' flex='0'> + <StyledListItemLabel $isSelected={isSelected}>{balance}</StyledListItemLabel> + <Span size='s' color='tertiary'> + {balanceUSD} + </Span> + </Flex> + </> + ); +}; + +export { TokenListItem }; +export type { TokenListItemProps }; diff --git a/src/component-library/TokenInput/TokenSelect.tsx b/src/component-library/TokenInput/TokenSelect.tsx index 36b7bf296a..b6efbb6393 100644 --- a/src/component-library/TokenInput/TokenSelect.tsx +++ b/src/component-library/TokenInput/TokenSelect.tsx @@ -1,28 +1,9 @@ import { CoinIcon } from '../CoinIcon'; import { Flex } from '../Flex'; import { Item, Select, SelectProps } from '../Select'; -import { useSelectModalContext } from '../Select/SelectModalContext'; import { Span } from '../Text'; -import { StyledListItemLabel, StyledListTokenWrapper, StyledTicker, StyledTokenSelect } from './TokenInput.style'; - -const ListItem = ({ data }: { data: TokenData }) => { - const isSelected = useSelectModalContext().selectedItem?.key === data.value; - - return ( - <> - <StyledListTokenWrapper alignItems='center' gap='spacing2' flex='1'> - <CoinIcon size={data.tickers ? 'lg' : 'md'} ticker={data.value} tickers={data.tickers} /> - <StyledListItemLabel $isSelected={isSelected}>{data.value}</StyledListItemLabel> - </StyledListTokenWrapper> - <Flex direction='column' alignItems='flex-end' gap='spacing2' flex='0'> - <StyledListItemLabel $isSelected={isSelected}>{data.balance}</StyledListItemLabel> - <Span size='s' color='tertiary'> - {data.balanceUSD} - </Span> - </Flex> - </> - ); -}; +import { StyledTicker, StyledTokenSelect } from './TokenInput.style'; +import { TokenListItem } from './TokenListItem'; const Value = ({ data }: { data: TokenData }) => ( <Flex alignItems='center' justifyContent='space-evenly' gap='spacing1'> @@ -38,7 +19,7 @@ type TokenData = { balanceUSD: string; }; -type TokenSelectProps = Omit<SelectProps<TokenData>, 'children' | 'type'>; +type TokenSelectProps = Omit<SelectProps<TokenData, 'modal'>, 'children' | 'type'>; const TokenSelect = ({ label: labelProp, 'aria-label': ariaLabelProp, ...props }: TokenSelectProps): JSX.Element => { // it is unlikely that labelProp is not a string, but we need to avoid any accessibility error @@ -46,7 +27,7 @@ const TokenSelect = ({ label: labelProp, 'aria-label': ariaLabelProp, ...props } const ariaLabel = labelText && `Choose token for ${labelText} field`; return ( - <Select<TokenData> + <Select<TokenData, 'modal'> {...props} type='modal' asSelectTrigger={StyledTokenSelect} @@ -57,7 +38,7 @@ const TokenSelect = ({ label: labelProp, 'aria-label': ariaLabelProp, ...props } > {(data: TokenData) => ( <Item key={data.value} textValue={data.value}> - <ListItem data={data} /> + <TokenListItem {...data} /> </Item> )} </Select> diff --git a/src/component-library/TokenInput/index.tsx b/src/component-library/TokenInput/index.tsx index f12f27264c..e1687df30e 100644 --- a/src/component-library/TokenInput/index.tsx +++ b/src/component-library/TokenInput/index.tsx @@ -1,3 +1,5 @@ export type { TokenInputProps } from './TokenInput'; export { TokenInput } from './TokenInput'; +export type { TokenListItemProps } from './TokenListItem'; +export { TokenListItem } from './TokenListItem'; export type { TokenData, TokenSelectProps } from './TokenSelect'; diff --git a/src/component-library/index.tsx b/src/component-library/index.tsx index e4e7692d57..f71642c245 100644 --- a/src/component-library/index.tsx +++ b/src/component-library/index.tsx @@ -59,8 +59,8 @@ export type { TextLinkProps } from './TextLink'; export { TextLink } from './TextLink'; export type { ComponentLibraryTheme } from './theme'; export { theme } from './theme'; -export type { TokenData, TokenInputProps, TokenSelectProps } from './TokenInput'; -export { TokenInput } from './TokenInput'; +export type { TokenData, TokenInputProps, TokenListItemProps, TokenSelectProps } from './TokenInput'; +export { TokenInput, TokenListItem } from './TokenInput'; export type { TokenStackProps } from './TokenStack'; export { TokenStack } from './TokenStack'; export type { TooltipProps } from './Tooltip'; diff --git a/src/components/TransactionDetails/TransactionDetails.style.tsx b/src/components/TransactionDetails/TransactionDetails.style.tsx index dc1f60e175..733c8a8256 100644 --- a/src/components/TransactionDetails/TransactionDetails.style.tsx +++ b/src/components/TransactionDetails/TransactionDetails.style.tsx @@ -1,7 +1,7 @@ import styled from 'styled-components'; import { InformationCircle } from '@/assets/icons'; -import { Dl, theme } from '@/component-library'; +import { Dl, Select, SelectProps, theme, TokenData } from '@/component-library'; const StyledDl = styled(Dl)` border: ${theme.border.default}; @@ -16,4 +16,11 @@ const StyledInformationCircle = styled(InformationCircle)` vertical-align: text-top; `; -export { StyledDl, StyledInformationCircle }; +const SelectWrapper = ({ ...props }: SelectProps<TokenData, 'modal'>) => <Select<TokenData, 'modal'> {...props} />; + +const StyledSelect = styled(SelectWrapper)` + flex-direction: row; + justify-content: space-between; +`; + +export { StyledDl, StyledInformationCircle, StyledSelect }; diff --git a/src/components/TransactionDetails/TransactionDetails.tsx b/src/components/TransactionDetails/TransactionDetails.tsx index fde51525ed..2be79d40a7 100644 --- a/src/components/TransactionDetails/TransactionDetails.tsx +++ b/src/components/TransactionDetails/TransactionDetails.tsx @@ -7,7 +7,7 @@ type TransactionDetailsProps = DlProps; const TransactionDetails = ({ children, direction = 'column', - gap = 'spacing3', + gap = 'spacing2', ...props }: TransactionDetailsProps): JSX.Element => ( <StyledDl direction={direction} gap={gap} {...props}> diff --git a/src/components/TransactionDetails/TransactionDetailsDt.tsx b/src/components/TransactionDetails/TransactionDetailsDt.tsx index 3d642afc7b..bf9f0bda48 100644 --- a/src/components/TransactionDetails/TransactionDetailsDt.tsx +++ b/src/components/TransactionDetails/TransactionDetailsDt.tsx @@ -22,7 +22,7 @@ const TransactionDetailsDt = ({ <Dt color={color} size={size} {...props}> {children} {tooltipLabel && ( - <Tooltip label='The bridge fee paid to the vaults, relayers and maintainers of the system'> + <Tooltip label={tooltipLabel}> <StyledInformationCircle size='s' /> </Tooltip> )} diff --git a/src/components/TransactionDetails/TransactionFeeSelect.tsx b/src/components/TransactionDetails/TransactionFeeSelect.tsx new file mode 100644 index 0000000000..249ca97226 --- /dev/null +++ b/src/components/TransactionDetails/TransactionFeeSelect.tsx @@ -0,0 +1,52 @@ +import { ReactNode } from 'react'; +import { useTranslation } from 'react-i18next'; + +import { Item, SelectProps, Span, TokenData, TokenListItem, Tooltip } from '@/component-library'; + +import { StyledInformationCircle, StyledSelect } from './TransactionDetails.style'; + +type Props = { + label: ReactNode; + tooltipLabel?: ReactNode; +}; + +type InheritAttrs = Omit<SelectProps<TokenData, 'modal'>, keyof Props | 'children'>; + +type TransactionFeeSelectProps = Props & InheritAttrs; + +const TransactionFeeSelect = ({ label: labelProp, tooltipLabel, ...props }: TransactionFeeSelectProps): JSX.Element => { + const { t } = useTranslation(); + + const label = tooltipLabel ? ( + <Span> + {labelProp} + {tooltipLabel && ( + <Tooltip label={tooltipLabel}> + <StyledInformationCircle size='s' /> + </Tooltip> + )} + </Span> + ) : ( + labelProp + ); + + return ( + <StyledSelect + type='modal' + size='small' + modalTitle={t('select_token')} + renderValue={(item) => item.value.value} + label={label} + {...props} + > + {(data: TokenData) => ( + <Item key={data.value} textValue={data.value}> + <TokenListItem {...data} /> + </Item> + )} + </StyledSelect> + ); +}; + +export { TransactionFeeSelect }; +export type { TransactionFeeSelectProps }; diff --git a/src/components/TransactionDetails/index.tsx b/src/components/TransactionDetails/index.tsx index 0c878c0b60..f2a80f8ccd 100644 --- a/src/components/TransactionDetails/index.tsx +++ b/src/components/TransactionDetails/index.tsx @@ -8,3 +8,5 @@ export type { TransactionDetailsGroupProps } from './TransactionDetailsGroup'; export { TransactionDetailsGroup } from './TransactionDetailsGroup'; export type { TransactionFeeProps } from './TransactionFee'; export { TransactionFee } from './TransactionFee'; +export type { TransactionFeeSelectProps } from './TransactionFeeSelect'; +export { TransactionFeeSelect } from './TransactionFeeSelect'; diff --git a/src/pages/Bridge/BridgeOverview/components/SelectVaultCard/VaultSelect.tsx b/src/pages/Bridge/BridgeOverview/components/SelectVaultCard/VaultSelect.tsx index cae06104f7..53f4615e44 100644 --- a/src/pages/Bridge/BridgeOverview/components/SelectVaultCard/VaultSelect.tsx +++ b/src/pages/Bridge/BridgeOverview/components/SelectVaultCard/VaultSelect.tsx @@ -5,13 +5,13 @@ import { BridgeVaultData } from '@/utils/hooks/api/bridge/use-get-vaults'; import { VaultListItem } from './VaultListItem'; -type VaultSelectProps = Omit<SelectProps<BridgeVaultData>, 'children' | 'type'>; +type VaultSelectProps = Omit<SelectProps<BridgeVaultData, 'modal'>, 'children' | 'type'>; const VaultSelect = (props: VaultSelectProps): JSX.Element => { const { t } = useTranslation(); return ( - <Select<BridgeVaultData> {...props} type='modal' modalTitle={t('bridge.select_vault')} size='large'> + <Select<BridgeVaultData, 'modal'> {...props} type='modal' modalTitle={t('bridge.select_vault')} size='large'> {(data: BridgeVaultData) => ( <Item key={data.id} textValue={data.vaultId.accountId.toString()}> <VaultListItem data={data} /> diff --git a/src/pages/Bridge/BridgeOverview/components/TransactionDetails/TransactionDetails.tsx b/src/pages/Bridge/BridgeOverview/components/TransactionDetails/TransactionDetails.tsx index 60060b8e7b..8520a54554 100644 --- a/src/pages/Bridge/BridgeOverview/components/TransactionDetails/TransactionDetails.tsx +++ b/src/pages/Bridge/BridgeOverview/components/TransactionDetails/TransactionDetails.tsx @@ -8,11 +8,13 @@ import { TransactionDetailsDd, TransactionDetailsDt, TransactionDetailsGroup, - TransactionFee + TransactionFee, + TransactionFeeSelect } from '@/components'; -import { TRANSACTION_FEE_AMOUNT } from '@/config/relay-chains'; +import { GOVERNANCE_TOKEN, TRANSACTION_FEE_AMOUNT } from '@/config/relay-chains'; import { getTokenPrice } from '@/utils/helpers/prices'; import { useGetPrices } from '@/utils/hooks/api/use-get-prices'; +import { useSelectCurrency } from '@/utils/hooks/use-select-currency'; import { StyledPlusDivider } from './TransactionDetails.style'; @@ -40,6 +42,8 @@ const TransactionDetails = ({ const prices = useGetPrices(); const { t } = useTranslation(); + const selectCurrency = useSelectCurrency(); + return ( <Flex direction='column' gap='spacing2'> <Flex direction='column'> @@ -76,19 +80,27 @@ const TransactionDetails = ({ </TransactionDetailsDd> </TransactionDetailsGroup> {securityDeposit && ( - <TransactionDetailsGroup> - <TransactionDetailsDt tooltipLabel={t('bridge.security_deposit_is_a_denial_of_service_protection')}> - {t('bridge.security_deposit')} - </TransactionDetailsDt> - <TransactionDetailsDd> - {securityDeposit.toHuman()} {securityDeposit.currency.ticker} ( - {displayMonetaryAmountInUSDFormat( - securityDeposit, - getTokenPrice(prices, securityDeposit.currency.ticker)?.usd - )} - ) - </TransactionDetailsDd> - </TransactionDetailsGroup> + <> + <TransactionFeeSelect + label='Security token' + items={selectCurrency.items} + value={GOVERNANCE_TOKEN.ticker} + tooltipLabel='djdkl' + /> + <TransactionDetailsGroup> + <TransactionDetailsDt tooltipLabel={t('bridge.security_deposit_is_a_denial_of_service_protection')}> + {t('bridge.security_deposit')} + </TransactionDetailsDt> + <TransactionDetailsDd> + {securityDeposit.toHuman()} {securityDeposit.currency.ticker} ( + {displayMonetaryAmountInUSDFormat( + securityDeposit, + getTokenPrice(prices, securityDeposit.currency.ticker)?.usd + )} + ) + </TransactionDetailsDd> + </TransactionDetailsGroup> + </> )} </BaseTransactionDetails> <BaseTransactionDetails> diff --git a/src/pages/Transfer/TransferForms/components/ChainSelect/ChainSelect.tsx b/src/pages/Transfer/TransferForms/components/ChainSelect/ChainSelect.tsx index 1506d894e6..bd97d52147 100644 --- a/src/pages/Transfer/TransferForms/components/ChainSelect/ChainSelect.tsx +++ b/src/pages/Transfer/TransferForms/components/ChainSelect/ChainSelect.tsx @@ -6,7 +6,7 @@ import { ChainData } from '@/types/chains'; import { ChainIcon } from '../ChainIcon'; import { StyledChain, StyledListChainWrapper, StyledListItemLabel } from './ChainSelect.style'; -type ChainSelectProps = Omit<SelectProps<ChainData>, 'children' | 'type'>; +type ChainSelectProps = Omit<SelectProps<ChainData, 'modal'>, 'children' | 'type'>; const ListItem = ({ data }: { data: ChainData }) => { const isSelected = useSelectModalContext().selectedItem?.key === data.id; @@ -31,7 +31,7 @@ const Value = ({ data }: { data: ChainData }) => ( const ChainSelect = ({ ...props }: ChainSelectProps): JSX.Element => { return ( <Flex direction='column' flex='1'> - <Select<ChainData> + <Select<ChainData, 'modal'> {...props} type='modal' renderValue={(item) => <Value data={item.value} />} From 7deba94057295890ebc846afa60b24d0e5fda26e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rui=20Sim=C3=A3o?= <rui.daniel.simao@gmail.com> Date: Thu, 15 Jun 2023 11:25:20 +0100 Subject: [PATCH 2/2] feat: final --- src/assets/locales/en/translation.json | 2 + .../Select/Select.stories.tsx | 2 +- src/component-library/Select/Select.tsx | 22 +++---- .../TokenInput/TokenSelect.tsx | 4 +- src/component-library/theme/theme.ts | 4 +- .../TransactionDetails.style.tsx | 21 ++++++- .../TransactionDetails/TransactionDetails.tsx | 9 +-- .../TransactionDetailsGroup.tsx | 8 ++- .../TransactionDetails/TransactionFee.tsx | 41 ------------- ...eSelect.tsx => TransactionSelectToken.tsx} | 16 ++++-- src/components/TransactionDetails/index.tsx | 6 +- .../TransactionFeeDetails.tsx | 57 +++++++++++++++++++ .../TransactionFeeDetails/index.tsx | 2 + src/components/index.tsx | 2 + .../SelectVaultCard/VaultSelect.tsx | 4 +- .../TransactionDetails/TransactionDetails.tsx | 32 +++++------ .../components/ChainSelect/ChainSelect.tsx | 4 +- .../components/TransferForm/TransferForm.tsx | 6 +- 18 files changed, 137 insertions(+), 105 deletions(-) delete mode 100644 src/components/TransactionDetails/TransactionFee.tsx rename src/components/TransactionDetails/{TransactionFeeSelect.tsx => TransactionSelectToken.tsx} (73%) create mode 100644 src/components/TransactionFeeDetails/TransactionFeeDetails.tsx create mode 100644 src/components/TransactionFeeDetails/index.tsx diff --git a/src/assets/locales/en/translation.json b/src/assets/locales/en/translation.json index 45f7524294..fce7c1fa44 100644 --- a/src/assets/locales/en/translation.json +++ b/src/assets/locales/en/translation.json @@ -160,6 +160,8 @@ "insufficient_token_balance": "Insufficient {{token}} balance", "amount": "Amount", "select_token": "Select Token", + "fee_token": "Fee token", + "fx_fees": "Tx fees", "redeem_page": { "maximum_in_single_request": "Max redeemable in single request", "redeem": "Redeem", diff --git a/src/component-library/Select/Select.stories.tsx b/src/component-library/Select/Select.stories.tsx index ebc724166b..a596980646 100644 --- a/src/component-library/Select/Select.stories.tsx +++ b/src/component-library/Select/Select.stories.tsx @@ -6,7 +6,7 @@ import { Flex } from '../Flex'; import { Select, SelectProps } from './Select'; import { SelectTrigger, SelectTriggerProps } from './SelectTrigger'; -const SelectTemplate: Story<SelectProps<any, 'modal'>> = (args) => { +const SelectTemplate: Story<SelectProps<'modal', any>> = (args) => { return ( <Select {...args}> <Item textValue='BTC' key='BTC'> diff --git a/src/component-library/Select/Select.tsx b/src/component-library/Select/Select.tsx index 67991c2167..e0c90be775 100644 --- a/src/component-library/Select/Select.tsx +++ b/src/component-library/Select/Select.tsx @@ -17,7 +17,7 @@ type SelectType = 'listbox' | 'modal'; type SelectObject = Record<string, unknown>; // TODO: listbox to be implemented -type Props<T extends SelectObject, F extends SelectType> = { +type Props<F extends SelectType = 'listbox', T = SelectObject> = { type?: F; open?: boolean; loading?: boolean; @@ -29,19 +29,21 @@ type Props<T extends SelectObject, F extends SelectType> = { modalTitle?: F extends 'modal' ? ReactNode : never; }; -type InheritAttrs<F extends SelectType, T extends SelectObject = any> = Omit< +type InheritAttrs<F extends SelectType = 'listbox', T = SelectObject> = Omit< CollectionBase<T> & FieldProps & AriaSelectProps<T>, - keyof Props<T, F> | 'isDisabled' | 'isLoading' | 'isOpen' | 'isRequired' | 'selectedKey' | 'defaultSelectedKey' + keyof Props<F, T> | 'isDisabled' | 'isLoading' | 'isOpen' | 'isRequired' | 'selectedKey' | 'defaultSelectedKey' >; -type NativeAttrs<T extends SelectObject, F extends SelectType> = Omit< +type NativeAttrs<F extends SelectType = 'listbox', T = SelectObject> = Omit< React.InputHTMLAttributes<Element>, - keyof Props<T, F> + keyof Props<F, T> >; -type SelectProps<T extends SelectObject, F extends SelectType> = Props<T, F> & NativeAttrs<T, F> & InheritAttrs<F, T>; +type SelectProps<F extends SelectType = 'listbox', T = SelectObject> = Props<F, T> & + NativeAttrs<F, T> & + InheritAttrs<F, T>; -const Select = <T extends SelectObject, F extends SelectType>( +const Select = <F extends SelectType = 'listbox', T extends SelectObject = SelectObject>( { value, defaultValue, @@ -62,7 +64,7 @@ const Select = <T extends SelectObject, F extends SelectType>( renderValue = (item) => item.rendered, items, ...props - }: SelectProps<T, F>, + }: SelectProps<F, T>, ref: ForwardedRef<HTMLInputElement> ): JSX.Element => { const inputRef = useDOMRef(ref); @@ -150,8 +152,8 @@ const Select = <T extends SelectObject, F extends SelectType>( ); }; -const _Select = forwardRef(Select) as <T extends SelectObject, F extends SelectType>( - props: SelectProps<T, F> & { ref?: React.ForwardedRef<HTMLInputElement> } +const _Select = forwardRef(Select) as <F extends SelectType = 'listbox', T extends SelectObject = SelectObject>( + props: SelectProps<F, T> & { ref?: React.ForwardedRef<HTMLInputElement> } ) => ReturnType<typeof Select>; Select.displayName = 'Select'; diff --git a/src/component-library/TokenInput/TokenSelect.tsx b/src/component-library/TokenInput/TokenSelect.tsx index b6efbb6393..a5ab25135a 100644 --- a/src/component-library/TokenInput/TokenSelect.tsx +++ b/src/component-library/TokenInput/TokenSelect.tsx @@ -19,7 +19,7 @@ type TokenData = { balanceUSD: string; }; -type TokenSelectProps = Omit<SelectProps<TokenData, 'modal'>, 'children' | 'type'>; +type TokenSelectProps = Omit<SelectProps<'modal', TokenData>, 'children' | 'type'>; const TokenSelect = ({ label: labelProp, 'aria-label': ariaLabelProp, ...props }: TokenSelectProps): JSX.Element => { // it is unlikely that labelProp is not a string, but we need to avoid any accessibility error @@ -27,7 +27,7 @@ const TokenSelect = ({ label: labelProp, 'aria-label': ariaLabelProp, ...props } const ariaLabel = labelText && `Choose token for ${labelText} field`; return ( - <Select<TokenData, 'modal'> + <Select<'modal', TokenData> {...props} type='modal' asSelectTrigger={StyledTokenSelect} diff --git a/src/component-library/theme/theme.ts b/src/component-library/theme/theme.ts index 28c007d3d0..22f6d5f2cf 100644 --- a/src/component-library/theme/theme.ts +++ b/src/component-library/theme/theme.ts @@ -577,8 +577,8 @@ const theme = { color: 'var(--color-select-text)', size: { small: { - padding: 'var(--spacing-1)', - text: 'var(--text-s)', + padding: 'var(--spacing-1) var(--spacing-2)', + text: 'var(--text-xs)', // TODO: to be determined maxHeight: 'calc(var(--spacing-6) - 1px)' }, diff --git a/src/components/TransactionDetails/TransactionDetails.style.tsx b/src/components/TransactionDetails/TransactionDetails.style.tsx index 733c8a8256..d93596d198 100644 --- a/src/components/TransactionDetails/TransactionDetails.style.tsx +++ b/src/components/TransactionDetails/TransactionDetails.style.tsx @@ -1,7 +1,7 @@ import styled from 'styled-components'; import { InformationCircle } from '@/assets/icons'; -import { Dl, Select, SelectProps, theme, TokenData } from '@/component-library'; +import { Dl, DlGroup, Select, SelectProps, theme, TokenData } from '@/component-library'; const StyledDl = styled(Dl)` border: ${theme.border.default}; @@ -16,11 +16,26 @@ const StyledInformationCircle = styled(InformationCircle)` vertical-align: text-top; `; -const SelectWrapper = ({ ...props }: SelectProps<TokenData, 'modal'>) => <Select<TokenData, 'modal'> {...props} />; +const SelectWrapper = ({ ...props }: SelectProps<'modal', TokenData>) => <Select<'modal', TokenData> {...props} />; const StyledSelect = styled(SelectWrapper)` flex-direction: row; justify-content: space-between; `; -export { StyledDl, StyledInformationCircle, StyledSelect }; +// This custom padding helps to keep harmony between normal elements and elements with small select +const StyledDlGroup = styled(DlGroup)` + &:first-of-type { + padding-bottom: 0.407rem; + } + + &:not(:first-of-type):not(:last-of-type) { + padding: 0.407rem 0; + } + + &:last-of-type { + padding-top: 0.407rem; + } +`; + +export { StyledDl, StyledDlGroup, StyledInformationCircle, StyledSelect }; diff --git a/src/components/TransactionDetails/TransactionDetails.tsx b/src/components/TransactionDetails/TransactionDetails.tsx index 2be79d40a7..8ef48e9a0f 100644 --- a/src/components/TransactionDetails/TransactionDetails.tsx +++ b/src/components/TransactionDetails/TransactionDetails.tsx @@ -4,13 +4,8 @@ import { StyledDl } from './TransactionDetails.style'; type TransactionDetailsProps = DlProps; -const TransactionDetails = ({ - children, - direction = 'column', - gap = 'spacing2', - ...props -}: TransactionDetailsProps): JSX.Element => ( - <StyledDl direction={direction} gap={gap} {...props}> +const TransactionDetails = ({ children, direction = 'column', ...props }: TransactionDetailsProps): JSX.Element => ( + <StyledDl direction={direction} gap='spacing0' {...props}> {children} </StyledDl> ); diff --git a/src/components/TransactionDetails/TransactionDetailsGroup.tsx b/src/components/TransactionDetails/TransactionDetailsGroup.tsx index 8d2d9020bd..764f582dd9 100644 --- a/src/components/TransactionDetails/TransactionDetailsGroup.tsx +++ b/src/components/TransactionDetails/TransactionDetailsGroup.tsx @@ -1,4 +1,6 @@ -import { DlGroup, DlGroupProps } from '@/component-library'; +import { DlGroupProps } from '@/component-library'; + +import { StyledDlGroup } from './TransactionDetails.style'; type TransactionDetailsGroupProps = DlGroupProps; @@ -6,7 +8,9 @@ const TransactionDetailsGroup = ({ flex = '1', justifyContent = 'space-between', ...props -}: TransactionDetailsGroupProps): JSX.Element => <DlGroup flex={flex} justifyContent={justifyContent} {...props} />; +}: TransactionDetailsGroupProps): JSX.Element => ( + <StyledDlGroup flex={flex} justifyContent={justifyContent} {...props} /> +); export { TransactionDetailsGroup }; export type { TransactionDetailsGroupProps }; diff --git a/src/components/TransactionDetails/TransactionFee.tsx b/src/components/TransactionDetails/TransactionFee.tsx deleted file mode 100644 index ccd9904213..0000000000 --- a/src/components/TransactionDetails/TransactionFee.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { CurrencyExt } from '@interlay/interbtc-api'; -import { MonetaryAmount } from '@interlay/monetary-js'; -import { ReactNode } from 'react'; -import { useTranslation } from 'react-i18next'; - -import { displayMonetaryAmountInUSDFormat } from '@/common/utils/utils'; -import { DlGroupProps } from '@/component-library'; -import { getTokenPrice } from '@/utils/helpers/prices'; -import { useGetPrices } from '@/utils/hooks/api/use-get-prices'; - -import { TransactionDetailsDd } from './TransactionDetailsDd'; -import { TransactionDetailsDt } from './TransactionDetailsDt'; -import { TransactionDetailsGroup } from './TransactionDetailsGroup'; - -type Props = { - label?: ReactNode; - tooltipLabel?: ReactNode; - amount: MonetaryAmount<CurrencyExt>; -}; - -type InheritAttrs = Omit<DlGroupProps, keyof Props>; - -type TransactionFeeProps = Props & InheritAttrs; - -const TransactionFee = ({ label, tooltipLabel, amount, ...props }: TransactionFeeProps): JSX.Element => { - const prices = useGetPrices(); - const { t } = useTranslation(); - - return ( - <TransactionDetailsGroup {...props}> - <TransactionDetailsDt tooltipLabel={tooltipLabel}>{label || t('fee')}</TransactionDetailsDt> - <TransactionDetailsDd> - {amount.toHuman()} {amount.currency.ticker} ( - {displayMonetaryAmountInUSDFormat(amount, getTokenPrice(prices, amount.currency.ticker)?.usd)}) - </TransactionDetailsDd> - </TransactionDetailsGroup> - ); -}; - -export { TransactionFee }; -export type { TransactionFeeProps }; diff --git a/src/components/TransactionDetails/TransactionFeeSelect.tsx b/src/components/TransactionDetails/TransactionSelectToken.tsx similarity index 73% rename from src/components/TransactionDetails/TransactionFeeSelect.tsx rename to src/components/TransactionDetails/TransactionSelectToken.tsx index 249ca97226..0f24c70a76 100644 --- a/src/components/TransactionDetails/TransactionFeeSelect.tsx +++ b/src/components/TransactionDetails/TransactionSelectToken.tsx @@ -6,15 +6,19 @@ import { Item, SelectProps, Span, TokenData, TokenListItem, Tooltip } from '@/co import { StyledInformationCircle, StyledSelect } from './TransactionDetails.style'; type Props = { - label: ReactNode; + label?: ReactNode; tooltipLabel?: ReactNode; }; -type InheritAttrs = Omit<SelectProps<TokenData, 'modal'>, keyof Props | 'children'>; +type InheritAttrs = Omit<SelectProps<'modal', TokenData>, keyof Props | 'children'>; -type TransactionFeeSelectProps = Props & InheritAttrs; +type TransactionSelectTokenProps = Props & InheritAttrs; -const TransactionFeeSelect = ({ label: labelProp, tooltipLabel, ...props }: TransactionFeeSelectProps): JSX.Element => { +const TransactionSelectToken = ({ + label: labelProp, + tooltipLabel, + ...props +}: TransactionSelectTokenProps): JSX.Element => { const { t } = useTranslation(); const label = tooltipLabel ? ( @@ -48,5 +52,5 @@ const TransactionFeeSelect = ({ label: labelProp, tooltipLabel, ...props }: Tran ); }; -export { TransactionFeeSelect }; -export type { TransactionFeeSelectProps }; +export { TransactionSelectToken }; +export type { TransactionSelectTokenProps }; diff --git a/src/components/TransactionDetails/index.tsx b/src/components/TransactionDetails/index.tsx index f2a80f8ccd..b81a57655b 100644 --- a/src/components/TransactionDetails/index.tsx +++ b/src/components/TransactionDetails/index.tsx @@ -6,7 +6,5 @@ export type { TransactionDetailsDtProps } from './TransactionDetailsDt'; export { TransactionDetailsDt } from './TransactionDetailsDt'; export type { TransactionDetailsGroupProps } from './TransactionDetailsGroup'; export { TransactionDetailsGroup } from './TransactionDetailsGroup'; -export type { TransactionFeeProps } from './TransactionFee'; -export { TransactionFee } from './TransactionFee'; -export type { TransactionFeeSelectProps } from './TransactionFeeSelect'; -export { TransactionFeeSelect } from './TransactionFeeSelect'; +export type { TransactionSelectTokenProps } from './TransactionSelectToken'; +export { TransactionSelectToken } from './TransactionSelectToken'; diff --git a/src/components/TransactionFeeDetails/TransactionFeeDetails.tsx b/src/components/TransactionFeeDetails/TransactionFeeDetails.tsx new file mode 100644 index 0000000000..df1ec77f14 --- /dev/null +++ b/src/components/TransactionFeeDetails/TransactionFeeDetails.tsx @@ -0,0 +1,57 @@ +import { CurrencyExt } from '@interlay/interbtc-api'; +import { MonetaryAmount } from '@interlay/monetary-js'; +import { mergeProps } from '@react-aria/utils'; +import { ReactNode } from 'react'; +import { useTranslation } from 'react-i18next'; + +import { displayMonetaryAmountInUSDFormat } from '@/common/utils/utils'; +import { getTokenPrice } from '@/utils/helpers/prices'; +import { useGetPrices } from '@/utils/hooks/api/use-get-prices'; + +import { + TransactionDetails, + TransactionDetailsDd, + TransactionDetailsDt, + TransactionDetailsGroup, + TransactionDetailsProps, + TransactionSelectToken, + TransactionSelectTokenProps +} from '../TransactionDetails'; + +type Props = { + amount: MonetaryAmount<CurrencyExt>; + label?: ReactNode; + tooltipLabel?: ReactNode; + selectProps?: TransactionSelectTokenProps; +}; + +type InheritAttrs = Omit<TransactionDetailsProps, keyof Props>; + +type TransactionFeeDetailsProps = Props & InheritAttrs; + +const TransactionFeeDetails = ({ + amount, + label, + tooltipLabel, + selectProps, + ...props +}: TransactionFeeDetailsProps): JSX.Element => { + const prices = useGetPrices(); + const { t } = useTranslation(); + + return ( + <TransactionDetails {...props}> + {selectProps && <TransactionSelectToken {...mergeProps({ label: t('fee_token') }, selectProps)} />} + <TransactionDetailsGroup> + <TransactionDetailsDt tooltipLabel={tooltipLabel}>{label || t('fx_fees')}</TransactionDetailsDt> + <TransactionDetailsDd> + {amount.toHuman()} {amount.currency.ticker} ( + {displayMonetaryAmountInUSDFormat(amount, getTokenPrice(prices, amount.currency.ticker)?.usd)}) + </TransactionDetailsDd> + </TransactionDetailsGroup> + </TransactionDetails> + ); +}; + +export { TransactionFeeDetails }; +export type { TransactionFeeDetailsProps }; diff --git a/src/components/TransactionFeeDetails/index.tsx b/src/components/TransactionFeeDetails/index.tsx new file mode 100644 index 0000000000..84405ffc89 --- /dev/null +++ b/src/components/TransactionFeeDetails/index.tsx @@ -0,0 +1,2 @@ +export type { TransactionFeeDetailsProps } from './TransactionFeeDetails'; +export { TransactionFeeDetails } from './TransactionFeeDetails'; diff --git a/src/components/index.tsx b/src/components/index.tsx index f62cbfaba6..fbd8dc51df 100644 --- a/src/components/index.tsx +++ b/src/components/index.tsx @@ -20,5 +20,7 @@ export { ReceivableAssets } from './ReceivableAssets'; export type { ToastContainerProps } from './ToastContainer'; export { ToastContainer } from './ToastContainer'; export * from './TransactionDetails'; +export type { TransactionFeeDetailsProps } from './TransactionFeeDetails'; +export { TransactionFeeDetails } from './TransactionFeeDetails'; export type { TransactionToastProps } from './TransactionToast'; export { TransactionToast } from './TransactionToast'; diff --git a/src/pages/Bridge/BridgeOverview/components/SelectVaultCard/VaultSelect.tsx b/src/pages/Bridge/BridgeOverview/components/SelectVaultCard/VaultSelect.tsx index 53f4615e44..7f71e9c14f 100644 --- a/src/pages/Bridge/BridgeOverview/components/SelectVaultCard/VaultSelect.tsx +++ b/src/pages/Bridge/BridgeOverview/components/SelectVaultCard/VaultSelect.tsx @@ -5,13 +5,13 @@ import { BridgeVaultData } from '@/utils/hooks/api/bridge/use-get-vaults'; import { VaultListItem } from './VaultListItem'; -type VaultSelectProps = Omit<SelectProps<BridgeVaultData, 'modal'>, 'children' | 'type'>; +type VaultSelectProps = Omit<SelectProps<'modal', BridgeVaultData>, 'children' | 'type'>; const VaultSelect = (props: VaultSelectProps): JSX.Element => { const { t } = useTranslation(); return ( - <Select<BridgeVaultData, 'modal'> {...props} type='modal' modalTitle={t('bridge.select_vault')} size='large'> + <Select<'modal', BridgeVaultData> {...props} type='modal' modalTitle={t('bridge.select_vault')} size='large'> {(data: BridgeVaultData) => ( <Item key={data.id} textValue={data.vaultId.accountId.toString()}> <VaultListItem data={data} /> diff --git a/src/pages/Bridge/BridgeOverview/components/TransactionDetails/TransactionDetails.tsx b/src/pages/Bridge/BridgeOverview/components/TransactionDetails/TransactionDetails.tsx index 8520a54554..8c057156be 100644 --- a/src/pages/Bridge/BridgeOverview/components/TransactionDetails/TransactionDetails.tsx +++ b/src/pages/Bridge/BridgeOverview/components/TransactionDetails/TransactionDetails.tsx @@ -8,8 +8,7 @@ import { TransactionDetailsDd, TransactionDetailsDt, TransactionDetailsGroup, - TransactionFee, - TransactionFeeSelect + TransactionFeeDetails } from '@/components'; import { GOVERNANCE_TOKEN, TRANSACTION_FEE_AMOUNT } from '@/config/relay-chains'; import { getTokenPrice } from '@/utils/helpers/prices'; @@ -81,12 +80,6 @@ const TransactionDetails = ({ </TransactionDetailsGroup> {securityDeposit && ( <> - <TransactionFeeSelect - label='Security token' - items={selectCurrency.items} - value={GOVERNANCE_TOKEN.ticker} - tooltipLabel='djdkl' - /> <TransactionDetailsGroup> <TransactionDetailsDt tooltipLabel={t('bridge.security_deposit_is_a_denial_of_service_protection')}> {t('bridge.security_deposit')} @@ -103,17 +96,18 @@ const TransactionDetails = ({ </> )} </BaseTransactionDetails> - <BaseTransactionDetails> - {bitcoinNetworkFee ? ( - <TransactionFee label={t('bridge.bitcoin_network_fee')} amount={bitcoinNetworkFee} /> - ) : ( - <TransactionFee - label={t('bridge.transaction_fee')} - tooltipLabel={t('bridge.fee_for_transaction_to_be_included_in_the_parachain')} - amount={TRANSACTION_FEE_AMOUNT} - /> - )} - </BaseTransactionDetails> + {bitcoinNetworkFee ? ( + <TransactionFeeDetails label={t('bridge.bitcoin_network_fee')} amount={bitcoinNetworkFee} /> + ) : ( + <TransactionFeeDetails + amount={TRANSACTION_FEE_AMOUNT} + selectProps={{ + value: GOVERNANCE_TOKEN.ticker, + onSelectionChange: console.log, + items: selectCurrency.items + }} + /> + )} </Flex> ); }; diff --git a/src/pages/Transfer/TransferForms/components/ChainSelect/ChainSelect.tsx b/src/pages/Transfer/TransferForms/components/ChainSelect/ChainSelect.tsx index bd97d52147..7359fb0188 100644 --- a/src/pages/Transfer/TransferForms/components/ChainSelect/ChainSelect.tsx +++ b/src/pages/Transfer/TransferForms/components/ChainSelect/ChainSelect.tsx @@ -6,7 +6,7 @@ import { ChainData } from '@/types/chains'; import { ChainIcon } from '../ChainIcon'; import { StyledChain, StyledListChainWrapper, StyledListItemLabel } from './ChainSelect.style'; -type ChainSelectProps = Omit<SelectProps<ChainData, 'modal'>, 'children' | 'type'>; +type ChainSelectProps = Omit<SelectProps<'modal', ChainData>, 'children' | 'type'>; const ListItem = ({ data }: { data: ChainData }) => { const isSelected = useSelectModalContext().selectedItem?.key === data.id; @@ -31,7 +31,7 @@ const Value = ({ data }: { data: ChainData }) => ( const ChainSelect = ({ ...props }: ChainSelectProps): JSX.Element => { return ( <Flex direction='column' flex='1'> - <Select<ChainData, 'modal'> + <Select<'modal', ChainData> {...props} type='modal' renderValue={(item) => <Value data={item.value} />} diff --git a/src/pages/Transfer/TransferForms/components/TransferForm/TransferForm.tsx b/src/pages/Transfer/TransferForms/components/TransferForm/TransferForm.tsx index 8cc10c9f08..a87a4237f4 100644 --- a/src/pages/Transfer/TransferForms/components/TransferForm/TransferForm.tsx +++ b/src/pages/Transfer/TransferForms/components/TransferForm/TransferForm.tsx @@ -7,7 +7,7 @@ import { useSelector } from 'react-redux'; import { StoreType } from '@/common/types/util.types'; import { convertMonetaryAmountToValueInUSD, newSafeMonetaryAmount } from '@/common/utils/utils'; import { Flex, Input, TokenInput } from '@/component-library'; -import { AuthCTA, TransactionDetails, TransactionFee } from '@/components'; +import { AuthCTA, TransactionFeeDetails } from '@/components'; import { GOVERNANCE_TOKEN, TRANSACTION_FEE_AMOUNT } from '@/config/relay-chains'; import { isFormDisabled, useForm } from '@/lib/form'; import { @@ -121,9 +121,7 @@ const TransferForm = (): JSX.Element => { /> </Flex> <Flex direction='column' gap='spacing4'> - <TransactionDetails> - <TransactionFee amount={TRANSACTION_FEE_AMOUNT} /> - </TransactionDetails> + <TransactionFeeDetails amount={TRANSACTION_FEE_AMOUNT} /> <AuthCTA type='submit' disabled={isBtnDisabled} size='large' loading={transaction.isLoading}> Transfer </AuthCTA>