diff --git a/src/app/components/account/account-list-item-layout.tsx b/src/app/components/account/account-list-item-layout.tsx index 5fde1bb6215..82cc25d2a59 100644 --- a/src/app/components/account/account-list-item-layout.tsx +++ b/src/app/components/account/account-list-item-layout.tsx @@ -1,58 +1,82 @@ +import { ComponentProps, ReactNode } from 'react'; + import { SettingsSelectors } from '@tests/selectors/settings.selectors'; -import { Flex, HStack, Stack, StackProps, styled } from 'leather-styles/jsx'; +import { HStack } from 'leather-styles/jsx'; import { useViewportMinWidth } from '@app/common/hooks/use-media-query'; import { BulletSeparator } from '@app/ui/components/bullet-separator/bullet-separator'; import { CheckmarkIcon } from '@app/ui/components/icons/checkmark-icon'; +import { Item } from '@app/ui/components/items/item'; +import { ItemDefaultLayout } from '@app/ui/components/items/item-default.layout'; import { Spinner } from '@app/ui/components/spinner'; import { truncateMiddle } from '@app/ui/utils/truncate-middle'; -import { Flag } from '../layout/flag'; import { StacksAccountLoader } from '../loaders/stacks-account-loader'; import { BitcoinNativeSegwitAccountLoader } from './bitcoin-account-loader'; -interface AccountListItemLayoutProps extends StackProps { - isLoading: boolean; - isActive: boolean; +interface AccountListItemLayoutProps extends ComponentProps<'div'> { + accountName: ReactNode; + avatar: ReactNode; + balanceLabel: ReactNode; index: number; - accountName: React.ReactNode; - avatar: React.JSX.Element; - balanceLabel: React.ReactNode; + isLoading: boolean; + isPressable?: boolean; + isSelected: boolean; onSelectAccount(): void; } export function AccountListItemLayout(props: AccountListItemLayoutProps) { const { - index, - isLoading, - isActive, accountName, avatar, balanceLabel, + index, + isLoading, + isPressable, + isSelected, onSelectAccount, - children = null, - ...rest } = props; const isBreakpointSm = useViewportMinWidth('sm'); return ( - - - - - - {accountName} - {isActive && } + + + {accountName} + {isSelected && } - {isLoading ? ( + } + contentLeftBottom={ + + + + {account => ( + + {truncateMiddle(account.address, isBreakpointSm ? 4 : 3)} + + )} + + + {signer => ( + + {truncateMiddle(signer.address, 5)} + + )} + + + + } + contentRightTop={ + isLoading ? ( ) : ( - balanceLabel - )} - - - - - {account => ( - - {truncateMiddle(account.address, isBreakpointSm ? 4 : 3)} - - )} - - - - {signer => ( - - {truncateMiddle(signer.address, 5)} - - )} - - - - - - {children} - + {balanceLabel} + ) + } + /> + + ); } diff --git a/src/app/components/bitcoin-transaction-item/bitcoin-transaction-item.tsx b/src/app/components/bitcoin-transaction-item/bitcoin-transaction-item.tsx index 227990cc93f..3838cd0a656 100644 --- a/src/app/components/bitcoin-transaction-item/bitcoin-transaction-item.tsx +++ b/src/app/components/bitcoin-transaction-item/bitcoin-transaction-item.tsx @@ -1,8 +1,6 @@ import { useMemo } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; -import { BoxProps } from 'leather-styles/jsx'; - import { BitcoinTx } from '@shared/models/transactions/bitcoin-transaction.model'; import { RouteUrls } from '@shared/route-urls'; @@ -15,7 +13,6 @@ import { isBitcoinTxInbound, } from '@app/common/transactions/bitcoin/utils'; import { openInNewTab } from '@app/common/utils/open-in-new-tab'; -import { usePressable } from '@app/components/item-hover'; import { IncreaseFeeButton } from '@app/components/stacks-transaction-item/increase-fee-button'; import { TransactionTitle } from '@app/components/transaction/transaction-title'; import { @@ -34,11 +31,10 @@ import { InscriptionIcon } from './bitcoin-transaction-inscription-icon'; import { BitcoinTransactionStatus } from './bitcoin-transaction-status'; import { BitcoinTransactionValue } from './bitcoin-transaction-value'; -interface BitcoinTransactionItemProps extends BoxProps { +interface BitcoinTransactionItemProps { transaction: BitcoinTx; } -export function BitcoinTransactionItem({ transaction, ...rest }: BitcoinTransactionItemProps) { - const [component, bind, { isHovered }] = usePressable(true); +export function BitcoinTransactionItem({ transaction }: BitcoinTransactionItemProps) { const { pathname } = useLocation(); const navigate = useNavigate(); @@ -92,7 +88,6 @@ export function BitcoinTransactionItem({ transaction, ...rest }: BitcoinTransact const increaseFeeButton = ( @@ -101,6 +96,7 @@ export function BitcoinTransactionItem({ transaction, ...rest }: BitcoinTransact return ( } txTitle={} txValue={txValue} - belowCaptionEl={increaseFeeButton} - {...bind} - {...rest} - > - {component} - + /> ); } diff --git a/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/brc20-token-asset-list.tsx b/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/brc20-token-asset-list.tsx index 5bd77800863..8652feb9670 100644 --- a/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/brc20-token-asset-list.tsx +++ b/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/brc20-token-asset-list.tsx @@ -3,11 +3,13 @@ import { useNavigate } from 'react-router-dom'; import { RouteUrls } from '@shared/route-urls'; import { noop } from '@shared/utils'; -import { Brc20TokenAssetItem } from '@app/components/crypto-assets/bitcoin/brc20-token-asset-list/components/brc20-token-asset-item'; import { useNativeSegwitBalance } from '@app/query/bitcoin/balance/btc-native-segwit-balance.hooks'; import { Brc20Token } from '@app/query/bitcoin/ordinals/brc20/brc20-tokens.query'; import { useCurrentAccountNativeSegwitAddressIndexZero } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; +import { Brc20TokenAssetItemLayout } from './components/brc20-token-asset-item.layout'; +import { Brc20AssetListLayout } from './components/brc20-token-asset-list.layout'; + export function Brc20TokenAssetList(props: { brc20Tokens?: Brc20Token[] }) { const navigate = useNavigate(); const currentAccountBtcAddress = useCurrentAccountNativeSegwitAddressIndexZero(); @@ -24,13 +26,18 @@ export function Brc20TokenAssetList(props: { brc20Tokens?: Brc20Token[] }) { } if (!props.brc20Tokens?.length) return null; - return props.brc20Tokens.map(token => ( - navigateToBrc20SendForm(token) : noop} - displayNotEnoughBalance={!hasPositiveBtcBalanceForFees} - key={token.tick} - /> - )); + + return ( + + {props.brc20Tokens?.map(token => ( + navigateToBrc20SendForm(token) : noop} + /> + ))} + + ); } diff --git a/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/components/brc20-token-asset-item.layout.tsx b/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/components/brc20-token-asset-item.layout.tsx index 0e9223ec493..750b729abc4 100644 --- a/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/components/brc20-token-asset-item.layout.tsx +++ b/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/components/brc20-token-asset-item.layout.tsx @@ -1,69 +1,52 @@ -import { BoxProps, Flex, HStack, styled } from 'leather-styles/jsx'; - -import type { Money } from '@shared/models/money.model'; +import { createMoney } from '@shared/models/money.model'; import { formatBalance } from '@app/common/format-balance'; -import { AssetCaption } from '@app/components/crypto-assets/components/asset-caption'; -import { usePressable } from '@app/components/item-hover'; -import { Flag } from '@app/components/layout/flag'; +import { Brc20Token } from '@app/query/bitcoin/ordinals/brc20/brc20-tokens.query'; import { Brc20TokenIcon } from '@app/ui/components/icons/brc20-token-icon'; +import { Item } from '@app/ui/components/items/item'; +import { ItemDefaultLayout } from '@app/ui/components/items/item-default.layout'; import { BasicTooltip } from '@app/ui/components/tooltip/basic-tooltip'; -interface Brc20TokenAssetItemLayoutProps extends BoxProps { - balance: Money; - caption: string; +interface Brc20TokenAssetItemLayoutProps { + token: Brc20Token; isPressable?: boolean; onClick?(): void; - title: string; displayNotEnoughBalance?: boolean; } export function Brc20TokenAssetItemLayout({ - balance, - caption, isPressable, onClick, - title, displayNotEnoughBalance, + token, }: Brc20TokenAssetItemLayoutProps) { - const [component, bind] = usePressable(isPressable); - + const balance = createMoney(Number(token.overall_balance), token.tick, 0); const formattedBalance = formatBalance(balance.amount.toString()); return ( - - } spacing="space.04" width="100%"> - - - {title} - - - - {formattedBalance.value} - - - - - - - {component} - - + + + } + contentLeftTop={{token.tick}} + contentLeftBottom={{'BRC-20'}} + contentRightTop={ + + {formattedBalance.value} + + } + /> + + ); } diff --git a/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/components/brc20-token-asset-item.tsx b/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/components/brc20-token-asset-item.tsx deleted file mode 100644 index 29820b11099..00000000000 --- a/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/components/brc20-token-asset-item.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { createMoney } from '@shared/models/money.model'; - -import { Brc20Token } from '@app/query/bitcoin/ordinals/brc20/brc20-tokens.query'; - -import { Brc20TokenAssetItemLayout } from './brc20-token-asset-item.layout'; - -interface Brc20TokenAssetItemProps { - token: Brc20Token; - isPressable?: boolean; - onClick?(): void; - displayNotEnoughBalance?: boolean; -} -export function Brc20TokenAssetItem({ - token, - isPressable, - onClick, - displayNotEnoughBalance, -}: Brc20TokenAssetItemProps) { - return ( - - ); -} diff --git a/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/components/brc20-token-asset-list.layout.tsx b/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/components/brc20-token-asset-list.layout.tsx new file mode 100644 index 00000000000..41ea4db9c38 --- /dev/null +++ b/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/components/brc20-token-asset-list.layout.tsx @@ -0,0 +1,10 @@ +import { CryptoAssetSelectors } from '@tests/selectors/crypto-asset.selectors'; +import { Stack, StackProps } from 'leather-styles/jsx'; + +export function Brc20AssetListLayout({ children }: StackProps) { + return ( + + {children} + + ); +} diff --git a/src/app/components/crypto-assets/choose-crypto-asset/crypto-asset-list-item.tsx b/src/app/components/crypto-assets/choose-crypto-asset/crypto-asset-list-item.tsx index e98f899f166..81eb70a1ecc 100644 --- a/src/app/components/crypto-assets/choose-crypto-asset/crypto-asset-list-item.tsx +++ b/src/app/components/crypto-assets/choose-crypto-asset/crypto-asset-list-item.tsx @@ -1,7 +1,6 @@ import type { AllTransferableCryptoAssetBalances } from '@shared/models/crypto-asset-balance.model'; -import { CryptoCurrencyAssetItem } from '@app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset-item'; - +import { CryptoCurrencyAssetItemLayout } from '../crypto-currency-asset/crypto-currency-asset-item.layout'; import { CryptoCurrencyAssetIcon } from './crypto-currency-asset-icon'; import { FungibleTokenAssetItem } from './fungible-token-asset-item'; @@ -16,7 +15,7 @@ export function CryptoAssetListItem(props: CryptoAssetListItemProps) { switch (type) { case 'crypto-currency': return ( - } isPressable diff --git a/src/app/components/crypto-assets/choose-crypto-asset/crypto-asset-list.layout.tsx b/src/app/components/crypto-assets/choose-crypto-asset/crypto-asset-list.layout.tsx index e1803d73421..c5c9418347b 100644 --- a/src/app/components/crypto-assets/choose-crypto-asset/crypto-asset-list.layout.tsx +++ b/src/app/components/crypto-assets/choose-crypto-asset/crypto-asset-list.layout.tsx @@ -3,12 +3,7 @@ import { Stack, StackProps } from 'leather-styles/jsx'; export function CryptoAssetListLayout({ children }: StackProps) { return ( - + {children} ); diff --git a/src/app/components/crypto-assets/choose-crypto-asset/crypto-asset-list.tsx b/src/app/components/crypto-assets/choose-crypto-asset/crypto-asset-list.tsx index fd1fb2f0e0f..d40c64ede1f 100644 --- a/src/app/components/crypto-assets/choose-crypto-asset/crypto-asset-list.tsx +++ b/src/app/components/crypto-assets/choose-crypto-asset/crypto-asset-list.tsx @@ -8,7 +8,7 @@ import { Brc20TokensLoader } from '@app/components/brc20-tokens-loader'; import { Brc20TokenAssetList } from '@app/components/crypto-assets/bitcoin/brc20-token-asset-list/brc20-token-asset-list'; import { BtcIcon } from '@app/ui/components/icons/btc-icon'; -import { CryptoCurrencyAssetItem } from '../crypto-currency-asset/crypto-currency-asset-item'; +import { CryptoCurrencyAssetItemLayout } from '../crypto-currency-asset/crypto-currency-asset-item.layout'; import { CryptoAssetListItem } from './crypto-asset-list-item'; import { CryptoAssetListLayout } from './crypto-asset-list.layout'; @@ -25,7 +25,7 @@ export function CryptoAssetList({ cryptoAssetBalances, onItemClick }: CryptoAsse {signer => ( {balance => ( - } onClick={() => onItemClick(balance)} diff --git a/src/app/components/crypto-assets/choose-crypto-asset/fungible-token-asset-item.tsx b/src/app/components/crypto-assets/choose-crypto-asset/fungible-token-asset-item.tsx index 0ce6b98c4ff..8d7389ea14f 100644 --- a/src/app/components/crypto-assets/choose-crypto-asset/fungible-token-asset-item.tsx +++ b/src/app/components/crypto-assets/choose-crypto-asset/fungible-token-asset-item.tsx @@ -2,7 +2,7 @@ import { FlexProps } from 'leather-styles/jsx'; import type { StacksFungibleTokenAssetBalance } from '@shared/models/crypto-asset-balance.model'; -import { StacksFungibleTokenAssetItem } from '@app/components/crypto-assets/stacks/fungible-token-asset/stacks-fungible-token-asset-item'; +import { StacksFungibleTokenAssetItemLayout } from '../stacks/fungible-token-asset/stacks-fungible-token-asset-item.layout'; interface FungibleTokenAssetItemProps extends FlexProps { assetBalance: StacksFungibleTokenAssetBalance; @@ -14,7 +14,11 @@ export function FungibleTokenAssetItem({ assetBalance, onClick }: FungibleTokenA switch (blockchain) { case 'stacks': return ( - + ); default: return null; diff --git a/src/app/components/crypto-assets/components/asset-caption.tsx b/src/app/components/crypto-assets/components/asset-caption.tsx deleted file mode 100644 index 36dd07d71d4..00000000000 --- a/src/app/components/crypto-assets/components/asset-caption.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { Flex, styled } from 'leather-styles/jsx'; - -interface AssetCaptionProps { - caption: string; -} -export function AssetCaption({ caption }: AssetCaptionProps) { - return ( - - {caption}{' '} - - ); -} diff --git a/src/app/components/crypto-assets/components/asset-row-grid.tsx b/src/app/components/crypto-assets/components/asset-row-grid.tsx deleted file mode 100644 index 3508934ab32..00000000000 --- a/src/app/components/crypto-assets/components/asset-row-grid.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { Grid, GridItem } from 'leather-styles/jsx'; - -interface AssetRowGridProps { - title: React.ReactNode; - balance: React.ReactNode; - caption: React.ReactNode; - usdBalance?: React.ReactNode; - rightElement?: React.ReactNode; -} -export function AssetRowGrid({ - title, - balance, - caption, - usdBalance, - rightElement, -}: AssetRowGridProps) { - const balanceItem = rightElement ? ( - - {rightElement} - - ) : ( - {balance} - ); - return ( - - - {title} - - {balanceItem} - {caption} - {usdBalance && {usdBalance}} - - ); -} diff --git a/src/app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset-item.layout.tsx b/src/app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset-item.layout.tsx index bf4a65215d5..d5ff281458e 100644 --- a/src/app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset-item.layout.tsx +++ b/src/app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset-item.layout.tsx @@ -1,103 +1,73 @@ -import { CryptoAssetSelectors } from '@tests/selectors/crypto-asset.selectors'; -import { Flex, styled } from 'leather-styles/jsx'; +import { ReactNode } from 'react'; -import { CryptoCurrencies } from '@shared/models/currencies.model'; -import { Money } from '@shared/models/money.model'; +import { AllCryptoCurrencyAssetBalances } from '@shared/models/crypto-asset-balance.model'; -import { formatBalance } from '@app/common/format-balance'; -import { ftDecimals } from '@app/common/stacks-utils'; -import { usePressable } from '@app/components/item-hover'; -import { Flag } from '@app/components/layout/flag'; +import { Item } from '@app/ui/components/items/item'; +import { ItemDefaultLayout } from '@app/ui/components/items/item-default.layout'; import { BasicTooltip } from '@app/ui/components/tooltip/basic-tooltip'; -import { truncateMiddle } from '@app/ui/utils/truncate-middle'; -import { AssetRowGrid } from '../components/asset-row-grid'; +import { parseCryptoCurrencyAssetBalance } from './crypto-currency-asset.utils'; interface CryptoCurrencyAssetItemLayoutProps { - balance: Money; - caption: string; + additionalBalanceInfo?: ReactNode; + additionalUsdBalanceInfo?: ReactNode; + address?: string; + assetBalance: AllCryptoCurrencyAssetBalances; icon: React.ReactNode; - copyIcon?: React.ReactNode; isPressable?: boolean; - title: string; - usdBalance?: string; - address?: string; - isHovered?: boolean; - currency?: CryptoCurrencies; - additionalBalanceInfo?: React.ReactNode; - additionalUsdBalanceInfo?: React.ReactNode; - rightElement?: React.ReactNode; onClick?(): void; + rightElement?: React.ReactNode; + usdBalance?: string; } export function CryptoCurrencyAssetItemLayout({ - balance, - caption, - icon, - copyIcon, - isPressable, - title, - usdBalance, - address = '', - isHovered = false, additionalBalanceInfo, additionalUsdBalanceInfo, - rightElement, + address = '', + assetBalance, + icon, + isPressable, onClick, + rightElement, + usdBalance, }: CryptoCurrencyAssetItemLayoutProps) { - const [component, bind] = usePressable(isPressable); - - const amount = balance.decimals - ? ftDecimals(balance.amount, balance.decimals) - : balance.amount.toString(); - const dataTestId = CryptoAssetSelectors.CryptoAssetListItem.replace( - '{symbol}', - balance.symbol.toLowerCase() - ); - const formattedBalance = formatBalance(amount); + const { balance, dataTestId, formattedBalance, title } = + parseCryptoCurrencyAssetBalance(assetBalance); return ( - - - - {isHovered ? truncateMiddle(address, 6) : title} - - } - balance={ - - - {formattedBalance.value} {additionalBalanceInfo} - - - } - caption={ - - {caption} - + + + {title}} + contentLeftBottom={{balance.symbol}} + contentRightTop={ + rightElement ? ( + rightElement + ) : ( + + + {formattedBalance.value} {additionalBalanceInfo} + + + ) } - usdBalance={ - - {balance.amount.toNumber() > 0 && address ? ( - - {usdBalance} - - ) : null} - {additionalUsdBalanceInfo} - + contentRightBottom={ + !rightElement && ( + <> + {balance.amount.toNumber() > 0 && address ? ( + {usdBalance} + ) : null} + {additionalUsdBalanceInfo} + + ) } - rightElement={rightElement} /> - - {component} - + + ); } diff --git a/src/app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset-item.tsx b/src/app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset-item.tsx deleted file mode 100644 index bddbae243ac..00000000000 --- a/src/app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset-item.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import type { AllCryptoCurrencyAssetBalances } from '@shared/models/crypto-asset-balance.model'; - -import { spamFilter } from '@app/common/utils/spam-filter'; - -import { CryptoCurrencyAssetItemLayout } from './crypto-currency-asset-item.layout'; - -interface CryptoCurrencyAssetItemProps { - assetBalance: AllCryptoCurrencyAssetBalances; - icon: React.ReactNode; - usdBalance?: string; - address?: string; - isPressable?: boolean; - additionalBalanceInfo?: React.ReactNode; - additionalUsdBalanceInfo?: React.ReactNode; - rightElement?: React.ReactNode; - onClick?(): void; -} -export function CryptoCurrencyAssetItem({ - additionalBalanceInfo, - additionalUsdBalanceInfo, - address, - assetBalance, - icon, - isPressable, - onClick, - rightElement, - usdBalance, -}: CryptoCurrencyAssetItemProps) { - const { balance, asset } = assetBalance; - - return ( - - ); -} diff --git a/src/app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset.utils.ts b/src/app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset.utils.ts new file mode 100644 index 00000000000..041ca288ea5 --- /dev/null +++ b/src/app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset.utils.ts @@ -0,0 +1,28 @@ +import { CryptoAssetSelectors } from '@tests/selectors/crypto-asset.selectors'; + +import { AllCryptoCurrencyAssetBalances } from '@shared/models/crypto-asset-balance.model'; + +import { formatBalance } from '@app/common/format-balance'; +import { ftDecimals } from '@app/common/stacks-utils'; +import { spamFilter } from '@app/common/utils/spam-filter'; + +export function parseCryptoCurrencyAssetBalance(assetBalance: AllCryptoCurrencyAssetBalances) { + const { asset, balance } = assetBalance; + + const amount = balance.decimals + ? ftDecimals(balance.amount, balance.decimals) + : balance.amount.toString(); + const dataTestId = CryptoAssetSelectors.CryptoAssetListItem.replace( + '{symbol}', + balance.symbol.toLowerCase() + ); + const formattedBalance = formatBalance(amount); + const title = spamFilter(asset.name); + + return { + balance, + dataTestId, + formattedBalance, + title, + }; +} diff --git a/src/app/components/crypto-assets/stacks/components/stacks-asset-avatar.tsx b/src/app/components/crypto-assets/stacks/components/stacks-asset-avatar.tsx index f731aea9a1f..08d771dd2d1 100644 --- a/src/app/components/crypto-assets/stacks/components/stacks-asset-avatar.tsx +++ b/src/app/components/crypto-assets/stacks/components/stacks-asset-avatar.tsx @@ -15,7 +15,7 @@ export function StacksAssetAvatar({ gradientString, imageCanonicalUri, isStx, - size = '36', + size = '40', ...props }: StacksAssetAvatarProps) { if (isStx) return ; diff --git a/src/app/components/crypto-assets/stacks/fungible-token-asset/stacks-fungible-token-asset-item.tsx b/src/app/components/crypto-assets/stacks/fungible-token-asset/fungible-token-asset.utils.ts similarity index 58% rename from src/app/components/crypto-assets/stacks/fungible-token-asset/stacks-fungible-token-asset-item.tsx rename to src/app/components/crypto-assets/stacks/fungible-token-asset/fungible-token-asset.utils.ts index 62c76e02d1c..06336273364 100644 --- a/src/app/components/crypto-assets/stacks/fungible-token-asset/stacks-fungible-token-asset-item.tsx +++ b/src/app/components/crypto-assets/stacks/fungible-token-asset/fungible-token-asset.utils.ts @@ -1,46 +1,41 @@ import { CryptoAssetSelectors } from '@tests/selectors/crypto-asset.selectors'; -import { FlexProps } from 'leather-styles/jsx'; import type { StacksFungibleTokenAssetBalance } from '@shared/models/crypto-asset-balance.model'; import { getImageCanonicalUri } from '@app/common/crypto-assets/stacks-crypto-asset.utils'; +import { formatBalance } from '@app/common/format-balance'; +import { ftDecimals } from '@app/common/stacks-utils'; import { formatContractId, getTicker } from '@app/common/utils'; import { spamFilter } from '@app/common/utils/spam-filter'; import { getAssetName } from '@app/ui/utils/get-asset-name'; -import { StacksFungibleTokenAssetItemLayout } from './stacks-fungible-token-asset-item.layout'; - -interface StacksFungibleTokenAssetItemProps extends FlexProps { - assetBalance: StacksFungibleTokenAssetBalance; - isPressable?: boolean; - onClick?(): void; -} -export function StacksFungibleTokenAssetItem({ - assetBalance, - isPressable, - onClick, -}: StacksFungibleTokenAssetItemProps) { +export function parseStacksFungibleTokenAssetBalance( + assetBalance: StacksFungibleTokenAssetBalance +) { const { asset, balance } = assetBalance; const { contractAddress, contractAssetName, contractName, name, symbol } = asset; + const amount = balance.decimals + ? ftDecimals(balance.amount, balance.decimals || 0) + : balance.amount.toString(); const avatar = `${formatContractId(contractAddress, contractName)}::${contractAssetName}`; const dataTestId = symbol && CryptoAssetSelectors.CryptoAssetListItem.replace('{symbol}', symbol.toLowerCase()); + const formattedBalance = formatBalance(amount); const friendlyName = name || (contractAssetName.includes('::') ? getAssetName(contractAssetName) : contractAssetName); const imageCanonicalUri = getImageCanonicalUri(asset.imageCanonicalUri, asset.name); const caption = symbol || getTicker(friendlyName); - return ( - - ); + const title = spamFilter(friendlyName); + + return { + amount, + avatar, + caption, + dataTestId, + formattedBalance, + imageCanonicalUri, + title, + }; } diff --git a/src/app/components/crypto-assets/stacks/fungible-token-asset/stacks-fungible-token-asset-item.layout.tsx b/src/app/components/crypto-assets/stacks/fungible-token-asset/stacks-fungible-token-asset-item.layout.tsx index a259f6fbf0b..d47bb580151 100644 --- a/src/app/components/crypto-assets/stacks/fungible-token-asset/stacks-fungible-token-asset-item.layout.tsx +++ b/src/app/components/crypto-assets/stacks/fungible-token-asset/stacks-fungible-token-asset-item.layout.tsx @@ -1,71 +1,52 @@ -import { Flex, FlexProps, styled } from 'leather-styles/jsx'; +import { StacksFungibleTokenAssetBalance } from '@shared/models/crypto-asset-balance.model'; -import type { Money } from '@shared/models/money.model'; - -import { formatBalance } from '@app/common/format-balance'; -import { ftDecimals } from '@app/common/stacks-utils'; import { StacksAssetAvatar } from '@app/components/crypto-assets/stacks/components/stacks-asset-avatar'; -import { usePressable } from '@app/components/item-hover'; -import { Flag } from '@app/components/layout/flag'; +import { Item } from '@app/ui/components/items/item'; +import { ItemDefaultLayout } from '@app/ui/components/items/item-default.layout'; import { BasicTooltip } from '@app/ui/components/tooltip/basic-tooltip'; -import { AssetCaption } from '../../components/asset-caption'; -import { AssetRowGrid } from '../../components/asset-row-grid'; +import { parseStacksFungibleTokenAssetBalance } from './fungible-token-asset.utils'; -interface StacksFungibleTokenAssetItemLayoutProps extends FlexProps { - avatar: string; - balance: Money; - caption: string; - imageCanonicalUri?: string; +interface StacksFungibleTokenAssetItemLayoutProps { + assetBalance: StacksFungibleTokenAssetBalance; isPressable?: boolean; - title: string; onClick?(): void; } export function StacksFungibleTokenAssetItemLayout({ - avatar, - balance, - caption, - imageCanonicalUri, + assetBalance, isPressable, - title, onClick, }: StacksFungibleTokenAssetItemLayoutProps) { - const [component, bind] = usePressable(isPressable); - - const amount = balance.decimals - ? ftDecimals(balance.amount, balance.decimals || 0) - : balance.amount.toString(); - const formattedBalance = formatBalance(amount); + const { amount, avatar, caption, dataTestId, formattedBalance, imageCanonicalUri, title } = + parseStacksFungibleTokenAssetBalance(assetBalance); return ( - - - {title[0]} - - } - spacing="space.04" - width="100%" - > - {title}} - balance={ - - - {formattedBalance.value} - + + + + {title[0]} + + } + contentLeftTop={{title}} + contentLeftBottom={{caption}} + contentRightTop={ + + {formattedBalance.value} } - caption={} /> - {component} - - + + ); } diff --git a/src/app/components/stacks-transaction-item/increase-fee-button.tsx b/src/app/components/stacks-transaction-item/increase-fee-button.tsx index 7cb383c8fef..cf924f4e988 100644 --- a/src/app/components/stacks-transaction-item/increase-fee-button.tsx +++ b/src/app/components/stacks-transaction-item/increase-fee-button.tsx @@ -4,13 +4,12 @@ import { ChevronsRightIcon } from '@app/ui/components/icons/chevrons-right-icon' interface IncreaseFeeButtonProps { isEnabled?: boolean; - isHovered: boolean; isSelected: boolean; onIncreaseFee(): void; } export function IncreaseFeeButton(props: IncreaseFeeButtonProps) { - const { isEnabled, isHovered, isSelected, onIncreaseFee } = props; - const isActive = isEnabled && isHovered && !isSelected; + const { isEnabled, isSelected, onIncreaseFee } = props; + const isActive = isEnabled && !isSelected; return ( @@ -99,16 +95,12 @@ export function StacksTransactionItem({ return ( } txValue={txValue} - belowCaptionEl={increaseFeeButton} - {...bind} - {...rest} - > - {component} - + /> ); } diff --git a/src/app/components/transaction-item/transaction-item.layout.tsx b/src/app/components/transaction-item/transaction-item.layout.tsx index bb961b93108..4248f843e04 100644 --- a/src/app/components/transaction-item/transaction-item.layout.tsx +++ b/src/app/components/transaction-item/transaction-item.layout.tsx @@ -1,49 +1,43 @@ import { ReactNode } from 'react'; -import { Box, Flex, HStack } from 'leather-styles/jsx'; +import { Item } from '@app/ui/components/items/item'; +import { ItemDefaultLayout } from '@app/ui/components/items/item-default.layout'; interface TransactionItemLayoutProps { openTxLink(): void; + rightElement?: ReactNode; txCaption: ReactNode; txTitle: ReactNode; txValue: ReactNode; txIcon?: ReactNode; txStatus?: ReactNode; - belowCaptionEl?: ReactNode; children?: ReactNode; } export function TransactionItemLayout({ openTxLink, + rightElement, txCaption, txIcon, txStatus, txTitle, txValue, - belowCaptionEl, - children, - ...rest }: TransactionItemLayoutProps) { return ( - - - {txIcon && txIcon} - - - {txTitle} {txValue} - - - {txCaption} {txStatus && txStatus} - {belowCaptionEl ? belowCaptionEl : null} - - - - {children} - + + + {txTitle}} + contentLeftBottom={ + <> + {txCaption} + {txStatus && txStatus} + + } + contentRightTop={rightElement ? rightElement : {txValue}} + /> + + ); } diff --git a/src/app/features/activity-list/components/pending-transaction-list/pending-transaction-list.layout.tsx b/src/app/features/activity-list/components/pending-transaction-list/pending-transaction-list.layout.tsx index 3d2fb787563..44acfa0e297 100644 --- a/src/app/features/activity-list/components/pending-transaction-list/pending-transaction-list.layout.tsx +++ b/src/app/features/activity-list/components/pending-transaction-list/pending-transaction-list.layout.tsx @@ -11,7 +11,7 @@ export function PendingTransactionListLayout({ children }: PendingTransactionLis Pending - + {children} diff --git a/src/app/features/activity-list/components/pending-transaction-list/pending-transaction-list.tsx b/src/app/features/activity-list/components/pending-transaction-list/pending-transaction-list.tsx index 6fa22d355a1..2fe58970c23 100644 --- a/src/app/features/activity-list/components/pending-transaction-list/pending-transaction-list.tsx +++ b/src/app/features/activity-list/components/pending-transaction-list/pending-transaction-list.tsx @@ -2,9 +2,9 @@ import { MempoolTransaction } from '@stacks/stacks-blockchain-api-types'; import { BitcoinTx } from '@shared/models/transactions/bitcoin-transaction.model'; +import { BitcoinTransactionItem } from '@app/components/bitcoin-transaction-item/bitcoin-transaction-item'; import { StacksTransactionItem } from '@app/components/stacks-transaction-item/stacks-transaction-item'; -import { BitcoinTransactionItem } from '../../../../components/bitcoin-transaction-item/bitcoin-transaction-item'; import { PendingTransactionListLayout } from './pending-transaction-list.layout'; interface PendingTransactionListProps { diff --git a/src/app/features/activity-list/components/transaction-list/transaction-list.layout.tsx b/src/app/features/activity-list/components/transaction-list/transaction-list.layout.tsx index eed16bebc44..7d3f7c2e50a 100644 --- a/src/app/features/activity-list/components/transaction-list/transaction-list.layout.tsx +++ b/src/app/features/activity-list/components/transaction-list/transaction-list.layout.tsx @@ -6,9 +6,5 @@ interface TransactionListLayoutProps { children: ReactNode; } export function TransactionListLayout({ children }: TransactionListLayoutProps) { - return ( - - {children} - - ); + return {children}; } diff --git a/src/app/features/activity-list/components/transaction-list/transactions-by-date.layout.tsx b/src/app/features/activity-list/components/transaction-list/transactions-by-date.layout.tsx index 117d795d447..f9b3bde1d8d 100644 --- a/src/app/features/activity-list/components/transaction-list/transactions-by-date.layout.tsx +++ b/src/app/features/activity-list/components/transaction-list/transactions-by-date.layout.tsx @@ -17,9 +17,7 @@ export function TransactionsByDateLayout({ {displayDate} - - {children} - + {children} ); } diff --git a/src/app/features/asset-list/asset-list.tsx b/src/app/features/asset-list/asset-list.tsx index d6703e2c534..096f86f20b0 100644 --- a/src/app/features/asset-list/asset-list.tsx +++ b/src/app/features/asset-list/asset-list.tsx @@ -7,7 +7,7 @@ import { useBtcAssetBalance } from '@app/common/hooks/balance/btc/use-btc-balanc import { useWalletType } from '@app/common/use-wallet-type'; import { BitcoinContractEntryPoint } from '@app/components/bitcoin-contract-entry-point/bitcoin-contract-entry-point'; import { Brc20TokensLoader } from '@app/components/brc20-tokens-loader'; -import { CryptoCurrencyAssetItem } from '@app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset-item'; +import { CryptoCurrencyAssetItemLayout } from '@app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset-item.layout'; import { CurrentStacksAccountLoader } from '@app/components/loaders/stacks-account-loader'; import { useHasBitcoinLedgerKeychain } from '@app/store/accounts/blockchain/bitcoin/bitcoin.ledger'; import { useCurrentAccountNativeSegwitAddressIndexZero } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; @@ -31,10 +31,10 @@ export function AssetsList() { const { whenWallet } = useWalletType(); return ( - + {whenWallet({ software: ( - } @@ -42,7 +42,7 @@ export function AssetsList() { /> ), ledger: ( - } diff --git a/src/app/features/asset-list/components/add-stacks-ledger-keys-item.tsx b/src/app/features/asset-list/components/add-stacks-ledger-keys-item.tsx index a09c669a03f..ff0f0ed281a 100644 --- a/src/app/features/asset-list/components/add-stacks-ledger-keys-item.tsx +++ b/src/app/features/asset-list/components/add-stacks-ledger-keys-item.tsx @@ -1,6 +1,6 @@ import BigNumber from 'bignumber.js'; -import { CryptoCurrencyAssetItem } from '@app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset-item'; +import { CryptoCurrencyAssetItemLayout } from '@app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset-item.layout'; import { StxAvatar } from '@app/components/crypto-assets/stacks/components/stx-avatar'; import { createStacksCryptoCurrencyAssetTypeWrapper } from '@app/query/stacks/balance/stacks-ft-balances.utils'; @@ -8,7 +8,7 @@ import { ConnectLedgerAssetBtn } from './connect-ledger-asset-button'; export function AddStacksLedgerKeysItem() { return ( - } rightElement={} diff --git a/src/app/features/asset-list/components/bitcoin-fungible-tokens-asset-list.tsx b/src/app/features/asset-list/components/bitcoin-fungible-tokens-asset-list.tsx index c8ce70ef4b9..e71915b3ee3 100644 --- a/src/app/features/asset-list/components/bitcoin-fungible-tokens-asset-list.tsx +++ b/src/app/features/asset-list/components/bitcoin-fungible-tokens-asset-list.tsx @@ -1,6 +1,6 @@ import { Stack } from 'leather-styles/jsx'; -import { Brc20TokenAssetItem } from '@app/components/crypto-assets/bitcoin/brc20-token-asset-list/components/brc20-token-asset-item'; +import { Brc20TokenAssetItemLayout } from '@app/components/crypto-assets/bitcoin/brc20-token-asset-list/components/brc20-token-asset-item.layout'; import { Brc20Token } from '@app/query/bitcoin/ordinals/brc20/brc20-tokens.query'; interface BitcoinFungibleTokenAssetListProps { @@ -12,7 +12,7 @@ export function BitcoinFungibleTokenAssetList({ brc20Tokens }: BitcoinFungibleTo return ( {brc20Tokens.map(token => ( - + ))} ); diff --git a/src/app/features/asset-list/components/stacks-asset-list.tsx b/src/app/features/asset-list/components/stacks-asset-list.tsx index 13019d672e4..a58c3a82834 100644 --- a/src/app/features/asset-list/components/stacks-asset-list.tsx +++ b/src/app/features/asset-list/components/stacks-asset-list.tsx @@ -2,7 +2,7 @@ import { styled } from 'leather-styles/jsx'; import { useStxBalance } from '@app/common/hooks/balance/stx/use-stx-balance'; import { ftDecimals } from '@app/common/stacks-utils'; -import { CryptoCurrencyAssetItem } from '@app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset-item'; +import { CryptoCurrencyAssetItemLayout } from '@app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset-item.layout'; import { StxAvatar } from '@app/components/crypto-assets/stacks/components/stx-avatar'; import { useStacksFungibleTokenAssetBalancesWithMetadata } from '@app/query/stacks/balance/stacks-ft-balances.hooks'; import { StacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.models'; @@ -36,7 +36,7 @@ export function StacksAssetList({ account }: StacksAssetListProps) { return ( <> - + {assetBalances.map(assetBalance => ( - diff --git a/src/app/features/collectibles/components/collectibes.layout.tsx b/src/app/features/collectibles/components/collectibes.layout.tsx index ccdae07f19e..091fa5196c5 100644 --- a/src/app/features/collectibles/components/collectibes.layout.tsx +++ b/src/app/features/collectibles/components/collectibes.layout.tsx @@ -23,7 +23,7 @@ export function CollectiblesLayout({ }: CollectiblesLayoutProps) { return ( <> - + {title} {isLoading ? ( diff --git a/src/app/features/increase-fee-drawer/components/increase-btc-fee-form.tsx b/src/app/features/increase-fee-drawer/components/increase-btc-fee-form.tsx index c9520a69f7b..bb6bff62f2a 100644 --- a/src/app/features/increase-fee-drawer/components/increase-btc-fee-form.tsx +++ b/src/app/features/increase-fee-drawer/components/increase-btc-fee-form.tsx @@ -50,7 +50,7 @@ export function IncreaseBtcFeeForm({ btcTx }: IncreaseBtcFeeFormProps) { validationSchema={validationSchema} > - {btcTx && } + {btcTx && } diff --git a/src/app/features/increase-fee-drawer/components/increase-stx-fee-form.tsx b/src/app/features/increase-fee-drawer/components/increase-stx-fee-form.tsx index a54b6467629..3b3a943cb29 100644 --- a/src/app/features/increase-fee-drawer/components/increase-stx-fee-form.tsx +++ b/src/app/features/increase-fee-drawer/components/increase-stx-fee-form.tsx @@ -93,7 +93,7 @@ export function IncreaseStxFeeForm() { > {props => ( - {tx && } + {tx && } {balances?.stx.unlockedStx.amount && ( diff --git a/src/app/features/leather-intro-dialog/leather-intro-steps.tsx b/src/app/features/leather-intro-dialog/leather-intro-steps.tsx index 29b62738bfc..48d8f80131a 100644 --- a/src/app/features/leather-intro-dialog/leather-intro-steps.tsx +++ b/src/app/features/leather-intro-dialog/leather-intro-steps.tsx @@ -16,7 +16,7 @@ export function LeatherIntroDialog({ children }: HasChildren) { return ( e.preventDefault()} onInteractOutside={e => e.preventDefault()} className={css({ maxWidth: '500px', backgroundColor: 'accent.background-primary' })} diff --git a/src/app/features/switch-account-drawer/components/switch-account-list-item.tsx b/src/app/features/switch-account-drawer/components/switch-account-list-item.tsx index 761823e7bc1..d5e77122983 100644 --- a/src/app/features/switch-account-drawer/components/switch-account-list-item.tsx +++ b/src/app/features/switch-account-drawer/components/switch-account-list-item.tsx @@ -5,7 +5,6 @@ import { useSwitchAccount } from '@app/common/hooks/account/use-switch-account'; import { useLoading } from '@app/common/hooks/use-loading'; import { AccountTotalBalance } from '@app/components/account-total-balance'; import { AccountListItemLayout } from '@app/components/account/account-list-item-layout'; -import { usePressable } from '@app/components/item-hover'; import { useNativeSegwitSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; import { useStacksAccounts } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; @@ -28,7 +27,6 @@ export const SwitchAccountListItem = memo( 'SWITCH_ACCOUNTS' + stacksAddress || bitcoinAddress ); const { handleSwitchAccount } = useSwitchAccount(handleClose); - const [component, bind] = usePressable(true); const name = useAccountDisplayName({ address: stacksAddress, index }); const handleClick = async () => { @@ -41,9 +39,7 @@ export const SwitchAccountListItem = memo( return ( {name}} avatar={ } - onSelectAccount={handleClick} - accountName={{name}} balanceLabel={ } - mt="space.05" - {...bind} - > - {component} - + index={index} + isLoading={isLoading} + isPressable + isSelected={currentAccountIndex === index} + onSelectAccount={handleClick} + /> ); } ); diff --git a/src/app/features/switch-account-drawer/components/switch-account-list.tsx b/src/app/features/switch-account-drawer/components/switch-account-list.tsx index 809dccd3804..0cafce7570f 100644 --- a/src/app/features/switch-account-drawer/components/switch-account-list.tsx +++ b/src/app/features/switch-account-drawer/components/switch-account-list.tsx @@ -23,7 +23,7 @@ export const SwitchAccountList = memo( style={{ paddingTop: '24px', height: '70vh' }} totalCount={addressesNum} itemContent={index => ( - + { - const [component, bind] = usePressable(true); const name = useAccountDisplayName(account); const btcAddress = useNativeSegwitAccountIndexAddressIndexZero(account.index); const accountSlug = useMemo(() => slugify(`Account ${account?.index + 1}`), [account?.index]); return ( - // Padding required on outer element to prevent jumpy list behaviours in - // virtualised list library + // Padding required on outer element to prevent jumpy virtualized list }> {name} @@ -65,13 +61,12 @@ const ChooseAccountItem = memo( balanceLabel={ } + data-testid={`account-${accountSlug}-${account.index}`} + index={account.index} isLoading={isLoading} + isSelected={false} onSelectAccount={() => onSelectAccount(account.index)} - data-testid={`account-${accountSlug}-${account.index}`} - {...bind} - > - {component} - + /> ); } diff --git a/src/app/pages/receive/components/receive-item.tsx b/src/app/pages/receive/components/receive-item.tsx index 95d97cec82c..a6d7e11f078 100644 --- a/src/app/pages/receive/components/receive-item.tsx +++ b/src/app/pages/receive/components/receive-item.tsx @@ -1,9 +1,10 @@ -import { Flex, HStack, Stack, styled } from 'leather-styles/jsx'; +import { HStack } from 'leather-styles/jsx'; -import { Flag } from '@app/components/layout/flag'; import { Button } from '@app/ui/components/button/button'; import { CopyIcon } from '@app/ui/components/icons/copy-icon'; import { QrCodeIcon } from '@app/ui/components/icons/qr-code-icon'; +import { Item } from '@app/ui/components/items/item'; +import { ItemWithButtonsLayout } from '@app/ui/components/items/item-with-buttons.layout'; import { truncateMiddle } from '@app/ui/utils/truncate-middle'; interface ReceiveItemProps { @@ -24,23 +25,33 @@ export function ReceiveItem({ }: ReceiveItemProps) { if (!address) return null; return ( - - - - {title} - {truncateMiddle(address, 6)} - - - - {onClickQrCode && ( - - )} - - - + + + {title}} + contentLeftBottom={ + {truncateMiddle(address, 6)} + } + contentRightTop={ + + + {onClickQrCode && ( + + )} + + } + /> + + ); } diff --git a/src/app/pages/receive/components/receive-items.tsx b/src/app/pages/receive/components/receive-items.tsx index d1c2d674ae9..4f89ecee966 100644 --- a/src/app/pages/receive/components/receive-items.tsx +++ b/src/app/pages/receive/components/receive-items.tsx @@ -13,7 +13,7 @@ export function ReceiveItemList({ children, title }: ReceiveItemListProps) { )} - + {children} diff --git a/src/app/pages/send/send-crypto-asset-form/components/recipient-accounts-drawer/account-list-item.tsx b/src/app/pages/send/send-crypto-asset-form/components/recipient-accounts-drawer/account-list-item.tsx index 971e0aa5bb5..c55652b6522 100644 --- a/src/app/pages/send/send-crypto-asset-form/components/recipient-accounts-drawer/account-list-item.tsx +++ b/src/app/pages/send/send-crypto-asset-form/components/recipient-accounts-drawer/account-list-item.tsx @@ -9,7 +9,6 @@ import { AccountTotalBalance } from '@app/components/account-total-balance'; import { AccountAvatarItem } from '@app/components/account/account-avatar-item'; import { AccountListItemLayout } from '@app/components/account/account-list-item-layout'; import { AccountNameLayout } from '@app/components/account/account-name'; -import { usePressable } from '@app/components/item-hover'; import { useNativeSegwitSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; import { StacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.models'; @@ -22,7 +21,6 @@ export const AccountListItem = memo(({ index, stacksAccount, onClose }: AccountL const { setFieldValue, values, setFieldTouched } = useFormikContext< BitcoinSendFormValues | StacksSendFormValues >(); - const [component, bind] = usePressable(true); const stacksAddress = stacksAccount?.address || ''; const name = useAccountDisplayName({ address: stacksAddress, index }); @@ -48,13 +46,10 @@ export const AccountListItem = memo(({ index, stacksAccount, onClose }: AccountL } balanceLabel={} index={index} - isActive={false} + isSelected={false} + isPressable isLoading={false} - mt="space.05" onSelectAccount={onSelectAccount} - {...bind} - > - {component} - + /> ); }); diff --git a/src/app/pages/swap/swap-choose-asset/components/swap-asset-item.layout.tsx b/src/app/pages/swap/swap-choose-asset/components/swap-asset-item.layout.tsx deleted file mode 100644 index 6f85aeafa2c..00000000000 --- a/src/app/pages/swap/swap-choose-asset/components/swap-asset-item.layout.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { Stack } from 'leather-styles/jsx'; - -import { HasChildren } from '@app/common/has-children'; -import { Flag } from '@app/components/layout/flag'; - -export function SwapAssetItemLayout({ - children, - icon, - ...rest -}: HasChildren & { icon: React.JSX.Element }) { - return ( - - {children} - - ); -} diff --git a/src/app/pages/swap/swap-choose-asset/components/swap-asset-item.tsx b/src/app/pages/swap/swap-choose-asset/components/swap-asset-item.tsx index 06b608c7194..2ef1aecb9df 100644 --- a/src/app/pages/swap/swap-choose-asset/components/swap-asset-item.tsx +++ b/src/app/pages/swap/swap-choose-asset/components/swap-asset-item.tsx @@ -1,19 +1,20 @@ -import { HStack, styled } from 'leather-styles/jsx'; +import { styled } from 'leather-styles/jsx'; import { formatMoneyWithoutSymbol } from '@app/common/money/format-money'; -import { usePressable } from '@app/components/item-hover'; import { useGetFungibleTokenMetadataQuery } from '@app/query/stacks/tokens/fungible-tokens/fungible-token-metadata.query'; import { isFtAsset } from '@app/query/stacks/tokens/token-metadata.utils'; +import { Item } from '@app/ui/components/items/item'; +import { ItemDefaultLayout } from '@app/ui/components/items/item-default.layout'; import { useAlexSdkBalanceAsFiat } from '../../hooks/use-alex-sdk-fiat-price'; import { SwapAsset } from '../../hooks/use-swap-form'; -import { SwapAssetItemLayout } from './swap-asset-item.layout'; interface SwapAssetItemProps { asset: SwapAsset; + dataTestId: string; + onClick(): void; } -export function SwapAssetItem({ asset }: SwapAssetItemProps) { - const [component, bind] = usePressable(true); +export function SwapAssetItem({ asset, dataTestId, onClick }: SwapAssetItemProps) { const balanceAsFiat = useAlexSdkBalanceAsFiat(asset.balance, asset.price); const { data: ftMetadata } = useGetFungibleTokenMetadataQuery(asset.principal); @@ -21,21 +22,17 @@ export function SwapAssetItem({ asset }: SwapAssetItemProps) { const displayName = asset.displayName ?? ftMetadataName; return ( - } - {...bind} - > - - {displayName} - {formatMoneyWithoutSymbol(asset.balance)} - - - {asset.name} - - {balanceAsFiat} - - - {component} - + + + } + contentLeftTop={{displayName}} + contentLeftBottom={{asset.name}} + contentRightTop={{formatMoneyWithoutSymbol(asset.balance)}} + contentRightBottom={{balanceAsFiat}} + /> + + ); } diff --git a/src/app/pages/swap/swap-choose-asset/components/swap-asset-list.layout.tsx b/src/app/pages/swap/swap-choose-asset/components/swap-asset-list.layout.tsx index e7feee61700..eb40bfdcbf4 100644 --- a/src/app/pages/swap/swap-choose-asset/components/swap-asset-list.layout.tsx +++ b/src/app/pages/swap/swap-choose-asset/components/swap-asset-list.layout.tsx @@ -2,7 +2,7 @@ import { Stack, StackProps } from 'leather-styles/jsx'; export function SwapAssetListLayout({ children }: StackProps) { return ( - + {children} ); diff --git a/src/app/pages/swap/swap-choose-asset/components/swap-asset-list.tsx b/src/app/pages/swap/swap-choose-asset/components/swap-asset-list.tsx index bc2875175ff..5419a1c2629 100644 --- a/src/app/pages/swap/swap-choose-asset/components/swap-asset-list.tsx +++ b/src/app/pages/swap/swap-choose-asset/components/swap-asset-list.tsx @@ -3,7 +3,6 @@ import { useNavigate } from 'react-router-dom'; import { SwapSelectors } from '@tests/selectors/swap.selectors'; import BigNumber from 'bignumber.js'; import { useFormikContext } from 'formik'; -import { styled } from 'leather-styles/jsx'; import { createMoney } from '@shared/models/money.model'; import { isUndefined } from '@shared/utils'; @@ -68,15 +67,12 @@ export function SwapAssetList({ assets }: SwapAssetList) { return ( {selectableAssets.map(asset => ( - onChooseAsset(asset)} - textAlign="left" - type="button" - > - - + /> ))} ); diff --git a/src/app/ui/components/button/button.stories.tsx b/src/app/ui/components/button/button.stories.tsx index c0d2277c292..796d2a6a9a0 100644 --- a/src/app/ui/components/button/button.stories.tsx +++ b/src/app/ui/components/button/button.stories.tsx @@ -26,6 +26,18 @@ export const Button: Story = { }, }; +export const Disabled: Story = { + parameters: { + controls: { include: ['size', 'variant'] }, + }, + args: { + disabled: true, + children: 'Button', + size: 'md', + variant: 'solid', + }, +}; + // TODO: Remove invert code export const InvertSolid: Story = { parameters: { diff --git a/src/app/ui/components/dynamic-color-circle.tsx b/src/app/ui/components/dynamic-color-circle.tsx index e0d62e71db4..2b4d0f0e495 100644 --- a/src/app/ui/components/dynamic-color-circle.tsx +++ b/src/app/ui/components/dynamic-color-circle.tsx @@ -6,7 +6,7 @@ interface DynamicColorCircleProps extends CircleProps { } export function DynamicColorCircle({ children, - sizeParam = '36', + sizeParam = '40', value, ...props }: DynamicColorCircleProps) { diff --git a/src/app/ui/components/items/item-default.layout.tsx b/src/app/ui/components/items/item-default.layout.tsx new file mode 100644 index 00000000000..ad9b7b0e7f5 --- /dev/null +++ b/src/app/ui/components/items/item-default.layout.tsx @@ -0,0 +1,59 @@ +import { ReactNode } from 'react'; + +import { Grid, GridItem } from 'leather-styles/jsx'; + +import { ChevronUpIcon } from '../icons/chevron-up-icon'; + +interface ItemDefaultLayoutProps { + contentLeftTop: ReactNode; + contentLeftBottom?: ReactNode; + contentRightTop: ReactNode; + contentRightBottom?: ReactNode; + flagImg?: ReactNode; + isDisabled?: boolean; + isPressable?: boolean; +} +export function ItemDefaultLayout({ + contentLeftTop, + contentLeftBottom, + contentRightTop, + contentRightBottom, + flagImg, + isDisabled, + isPressable, +}: ItemDefaultLayoutProps) { + return ( + + + {flagImg} + + + {contentLeftTop} + + + {contentRightTop} + + + {isPressable && ( + + )} + + {/* Using scroll here for now bc possibly shows addresses */} + + {contentLeftBottom} + + {contentRightBottom && {contentRightBottom}} + + ); +} diff --git a/src/app/ui/components/items/item-with-buttons.layout.tsx b/src/app/ui/components/items/item-with-buttons.layout.tsx new file mode 100644 index 00000000000..7900957c59e --- /dev/null +++ b/src/app/ui/components/items/item-with-buttons.layout.tsx @@ -0,0 +1,45 @@ +import { ReactNode } from 'react'; + +import { Grid, GridItem } from 'leather-styles/jsx'; + +interface ItemWithButtonsLayoutProps { + contentLeftTop: ReactNode; + contentLeftBottom?: ReactNode; + contentRightTop: ReactNode; + contentRightBottom?: ReactNode; + flagImg?: ReactNode; + isDisabled?: boolean; + isPressable?: boolean; +} +export function ItemWithButtonsLayout({ + contentLeftTop, + contentLeftBottom, + contentRightTop, + flagImg, +}: ItemWithButtonsLayoutProps) { + return ( + + + {flagImg} + + + {contentLeftTop} + + + {contentRightTop} + + {/* Using scroll here for now bc possibly shows addresses */} + + {contentLeftBottom} + + + ); +} diff --git a/src/app/ui/components/items/item.stories.tsx b/src/app/ui/components/items/item.stories.tsx new file mode 100644 index 00000000000..f739c1ce5b9 --- /dev/null +++ b/src/app/ui/components/items/item.stories.tsx @@ -0,0 +1,147 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { HStack } from 'leather-styles/jsx'; + +import { Button } from '../button/button'; +import { BtcIcon } from '../icons/btc-icon'; +import { CheckmarkIcon } from '../icons/checkmark-icon'; +import { CopyIcon } from '../icons/copy-icon'; +import { QrCodeIcon } from '../icons/qr-code-icon'; +import { Item } from './item'; +import { ItemDefaultLayout } from './item-default.layout'; +import { ItemWithButtonsLayout } from './item-with-buttons.layout'; + +const meta: Meta = { + component: Item.Root, + tags: ['autodocs'], + title: 'Item', +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: () => ( + // eslint-disable-next-line no-console + + + } + contentLeftTop={Label} + contentLeftBottom={Caption} + contentRightTop={1,000.00 ABC} + contentRightBottom={$1,000.00} + /> + + + ), +}; + +export const DefaultPressable: Story = { + render: () => ( + // eslint-disable-next-line no-console + console.log(`I'm interactive`)}> + + } + contentLeftTop={Label} + contentLeftBottom={Caption} + contentRightTop={1,000.00 ABC} + contentRightBottom={$1,000.00} + /> + + + ), +}; + +export const Selected: Story = { + render: () => ( + // eslint-disable-next-line no-console + console.log(`I'm interactive`)}> + + } + contentLeftTop={ + + Label + + + } + contentLeftBottom={Caption} + contentRightTop={1,000.00 ABC} + contentRightBottom={$1,000.00} + /> + + + ), +}; + +export const Disabled: Story = { + render: () => ( + // eslint-disable-next-line no-console + console.log(`I'm interactive`)}> + + } + contentLeftTop={Label} + contentLeftBottom={Caption} + contentRightTop={1,000.00 ABC} + contentRightBottom={$1,000.00} + /> + + + ), +}; + +export const WithButtons: Story = { + render: () => ( + // eslint-disable-next-line no-console + + + } + contentLeftTop={Label} + contentLeftBottom={Caption} + contentRightTop={ + + + + + } + /> + + + ), +}; + +export const WithButtonsDisabled: Story = { + render: () => ( + // eslint-disable-next-line no-console + + + } + contentLeftTop={Label} + contentLeftBottom={Caption} + contentRightTop={ + + + + + } + /> + + + ), +}; diff --git a/src/app/ui/components/items/item.tsx b/src/app/ui/components/items/item.tsx new file mode 100644 index 00000000000..eec33764e1e --- /dev/null +++ b/src/app/ui/components/items/item.tsx @@ -0,0 +1,137 @@ +import { type ComponentProps, KeyboardEvent } from 'react'; + +import { sva } from 'leather-styles/css'; +import { BoxProps, styled } from 'leather-styles/jsx'; + +import { createStyleContext } from '@app/ui/utils/style-context'; + +const item = sva({ + slots: ['root', 'content', 'text', 'caption'], + base: { + root: { + bg: 'accent.background-primary', + height: 'auto', + userSelect: 'none', + p: 'space.03', + width: '100%', + }, + content: { + width: '100%', + }, + text: { + color: 'accent.text-primary', + fontWeight: 500, + textStyle: 'label.02', + }, + caption: { + color: 'accent.text-subdued', + }, + }, + variants: { + isDisabled: { + true: { + root: { + _active: { bg: 'unset' }, + _focus: { border: 'unset' }, + _hover: { bg: 'unset' }, + cursor: 'not-allowed', + }, + content: {}, + text: { + color: 'accent.non-interactive', + }, + caption: { + color: 'accent.non-interactive', + }, + }, + }, + isPressable: { + true: { + root: { + _active: { + bg: 'accent.component-background-pressed', + }, + _focus: { + border: '2px solid {focus}', + }, + _hover: { + bg: 'accent.component-background-hover', + }, + }, + }, + }, + isSelected: { + true: { + root: { + _active: { + bg: 'unset', + }, + _focus: { + border: 'unset', + }, + _hover: { + bg: 'unset', + }, + }, + }, + }, + }, + compoundVariants: [ + { + isDisabled: true, + isPressable: true, + css: { + root: { + _active: { bg: 'unset' }, + _focus: { border: 'unset' }, + _hover: { bg: 'unset' }, + cursor: 'not-allowed', + }, + }, + }, + ], +}); + +const { withProvider, withContext } = createStyleContext(item); + +const RootBase = withProvider('div', 'root'); + +interface RootProps extends ComponentProps<'div'> { + isDisabled?: boolean; + isPressable?: boolean; + isSelected?: boolean; + onClick?(): void; +} +function Root({ isDisabled, isPressable, isSelected, onClick, ...props }: RootProps & BoxProps) { + const isRoleButton = !!onClick; + + function onKeyDown(e: KeyboardEvent) { + if (!isRoleButton) return; + if (e.key === 'enter' || e.key === ' ') { + e.preventDefault(); + onClick(); + } + } + + return ( + + ); +} + +const Content = withContext(styled('div'), 'content'); + +const Text = withContext(styled('span'), 'text'); + +const Caption = withContext(styled('span'), 'caption'); + +export const Item = { Root, Content, Text, Caption }; diff --git a/src/app/ui/components/link/link.stories.tsx b/src/app/ui/components/link/link.stories.tsx index b92fd85c7b8..8048b8a5091 100644 --- a/src/app/ui/components/link/link.stories.tsx +++ b/src/app/ui/components/link/link.stories.tsx @@ -22,6 +22,18 @@ export const Link: Story = { }, }; +export const Disabled: Story = { + parameters: { + controls: { include: ['size', 'variant'] }, + }, + args: { + children: 'Link', + disabled: true, + size: 'md', + variant: 'underlined', + }, +}; + // TODO: Remove invert code export const InvertLink: Story = { parameters: { diff --git a/src/app/ui/components/link/link.tsx b/src/app/ui/components/link/link.tsx index ff714540708..6890f139d90 100644 --- a/src/app/ui/components/link/link.tsx +++ b/src/app/ui/components/link/link.tsx @@ -9,12 +9,12 @@ type LinkProps = Omit, keyof LinkVariant LinkVariantProps; export const Link = forwardRef((props: LinkProps, ref: ForwardedRef) => { - const { children, fullWidth, invert, size, variant, ...rest } = props; + const { children, disabled, fullWidth, invert, size, variant, ...rest } = props; return ( diff --git a/src/app/ui/components/tooltip/basic-tooltip.tsx b/src/app/ui/components/tooltip/basic-tooltip.tsx index e10e2d61616..56fcead792f 100644 --- a/src/app/ui/components/tooltip/basic-tooltip.tsx +++ b/src/app/ui/components/tooltip/basic-tooltip.tsx @@ -11,7 +11,6 @@ interface BasicTooltipProps { side?: RadixTooltip.TooltipContentProps['side']; asChild?: boolean; } - export function BasicTooltip({ children, label, disabled, side, asChild }: BasicTooltipProps) { const isDisabled = !label || disabled; return ( diff --git a/theme/recipes/button-recipe.ts b/theme/recipes/button-recipe.ts index 5e26e867360..36da0ac019b 100644 --- a/theme/recipes/button-recipe.ts +++ b/theme/recipes/button-recipe.ts @@ -30,9 +30,6 @@ export const buttonRecipe = defineRecipe({ className: 'button', jsx: ['Button'], base: { - _disabled: { - cursor: 'not-allowed', - }, position: 'relative', rounded: 'xs', textStyle: 'label.02', @@ -54,8 +51,10 @@ export const buttonRecipe = defineRecipe({ bg: 'accent.action-primary-default', }, _disabled: { + _hover: { bg: 'accent.background-secondary' }, bg: 'accent.background-secondary', color: 'accent.non-interactive', + cursor: 'not-allowed', }, _focus: { _before: { @@ -74,6 +73,12 @@ export const buttonRecipe = defineRecipe({ _active: { bg: 'accent.component-background-pressed', }, + _disabled: { + _hover: { bg: 'unset' }, + border: '1px solid {colors.accent.non-interactive}', + color: 'accent.non-interactive', + cursor: 'not-allowed', + }, _focus: { _before: { border: '3px solid {colors.focus}', @@ -90,6 +95,11 @@ export const buttonRecipe = defineRecipe({ _active: { bg: 'accent.component-background-pressed', }, + _disabled: { + _hover: { bg: 'unset' }, + color: 'accent.non-interactive', + cursor: 'not-allowed', + }, _focus: { _before: { border: '3px solid {focus}', diff --git a/theme/recipes/link-recipe.ts b/theme/recipes/link-recipe.ts index 88ba089d678..f64aba9716a 100644 --- a/theme/recipes/link-recipe.ts +++ b/theme/recipes/link-recipe.ts @@ -6,11 +6,7 @@ export const linkRecipe = defineRecipe({ className: 'link', jsx: ['Link'], base: { - _disabled: { - cursor: 'not-allowed', - }, appearance: 'none', - color: 'accent.text-primary', display: 'inline', mb: 'space.01', p: 'unset', @@ -48,12 +44,6 @@ export const linkRecipe = defineRecipe({ }, color: 'accent.text-primary', }, - _disabled: { - _before: { - background: 'accent.non-interactive', - }, - color: 'accent.non-interactive', - }, _focus: { _before: { background: 'focus' }, color: 'accent.text-primary', @@ -64,6 +54,7 @@ export const linkRecipe = defineRecipe({ background: 'accent.action-primary-hover', }, }, + color: 'accent.text-primary', }, text: { @@ -84,13 +75,6 @@ export const linkRecipe = defineRecipe({ color: 'accent.text-primary', visibility: 'visible', }, - _disabled: { - _before: { - background: 'accent.non-interactive', - visibility: 'visible', - }, - color: 'accent.non-interactive', - }, _focus: { _before: { background: 'focus', @@ -105,12 +89,13 @@ export const linkRecipe = defineRecipe({ visibility: 'visible', }, }, + color: 'accent.text-primary', }, }, // TODO: Remove invert code invert: { true: {} }, - + disabled: { true: {} }, fullWidth: { true: { width: '100%' } }, }, @@ -122,8 +107,6 @@ export const linkRecipe = defineRecipe({ // TODO: Remove invert code compoundVariants: [ { - variant: 'underlined', - invert: true, css: { _focus: { _before: { @@ -140,6 +123,43 @@ export const linkRecipe = defineRecipe({ }, color: 'accent.background-secondary', }, + invert: true, + variant: 'underlined', + }, + { + css: { + _before: { + content: '""', + background: 'accent.non-interactive', + bottom: '-2px', + height: '2px', + left: 0, + position: 'absolute', + right: 0, + }, + color: 'accent.non-interactive', + cursor: 'not-allowed', + }, + disabled: true, + variant: 'underlined', + }, + { + css: { + _before: { + content: '""', + background: 'accent.non-interactive', + bottom: '-2px', + height: '2px', + left: 0, + position: 'absolute', + right: 0, + visibility: 'visible', + }, + color: 'accent.non-interactive', + cursor: 'not-allowed', + }, + disabled: true, + variant: 'text', }, ], });