diff --git a/panda.config.ts b/panda.config.ts index c2ed4de2c60..a078b7f55fc 100644 --- a/panda.config.ts +++ b/panda.config.ts @@ -4,6 +4,7 @@ import { breakpoints } from './theme/breakpoints'; import { globalCss } from './theme/global/global'; import { keyframes } from './theme/keyframes'; import { buttonRecipe } from './theme/recipes/button-recipe'; +import { itemRecipe } from './theme/recipes/item-recipe'; import { linkRecipe } from './theme/recipes/link-recipe'; import { semanticTokens } from './theme/semantic-tokens'; import { tokens } from './theme/tokens'; @@ -33,7 +34,7 @@ export default defineConfig({ keyframes, textStyles, breakpoints, - recipes: { button: buttonRecipe, link: linkRecipe }, + recipes: { button: buttonRecipe, item: itemRecipe, link: linkRecipe }, }, }, outdir: 'leather-styles', diff --git a/src/app/components/account/account-addresses.tsx b/src/app/components/account/account-addresses.tsx new file mode 100644 index 00000000000..9a2fda9ec52 --- /dev/null +++ b/src/app/components/account/account-addresses.tsx @@ -0,0 +1,28 @@ +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 { truncateMiddle } from '@app/ui/utils/truncate-middle'; + +import { StacksAccountLoader } from '../loaders/stacks-account-loader'; +import { BitcoinNativeSegwitAccountLoader } from './bitcoin-account-loader'; + +interface AccountAddressesProps { + index: number; +} +export function AcccountAddresses({ index }: AccountAddressesProps) { + const isBreakpointSm = useViewportMinWidth('sm'); + + return ( + + + + {account => <>{truncateMiddle(account.address, isBreakpointSm ? 4 : 3)}} + + + {signer => <>{truncateMiddle(signer.address, 5)}} + + + + ); +} diff --git a/src/app/components/account/account-list-item-layout.tsx b/src/app/components/account/account-list-item-layout.tsx deleted file mode 100644 index 82cc25d2a59..00000000000 --- a/src/app/components/account/account-list-item-layout.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { ComponentProps, ReactNode } from 'react'; - -import { SettingsSelectors } from '@tests/selectors/settings.selectors'; -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 { StacksAccountLoader } from '../loaders/stacks-account-loader'; -import { BitcoinNativeSegwitAccountLoader } from './bitcoin-account-loader'; - -interface AccountListItemLayoutProps extends ComponentProps<'div'> { - accountName: ReactNode; - avatar: ReactNode; - balanceLabel: ReactNode; - index: number; - isLoading: boolean; - isPressable?: boolean; - isSelected: boolean; - onSelectAccount(): void; -} -export function AccountListItemLayout(props: AccountListItemLayoutProps) { - const { - accountName, - avatar, - balanceLabel, - index, - isLoading, - isPressable, - isSelected, - onSelectAccount, - } = props; - - const isBreakpointSm = useViewportMinWidth('sm'); - - return ( - - - - {accountName} - {isSelected && } - - } - contentLeftBottom={ - - - - {account => ( - - {truncateMiddle(account.address, isBreakpointSm ? 4 : 3)} - - )} - - - {signer => ( - - {truncateMiddle(signer.address, 5)} - - )} - - - - } - contentRightTop={ - isLoading ? ( - - ) : ( - {balanceLabel} - ) - } - /> - - - ); -} diff --git a/src/app/components/account/account-list-item.layout.tsx b/src/app/components/account/account-list-item.layout.tsx new file mode 100644 index 00000000000..8d4ecf3b2ac --- /dev/null +++ b/src/app/components/account/account-list-item.layout.tsx @@ -0,0 +1,59 @@ +import { ComponentProps, ReactNode } from 'react'; + +import { SettingsSelectors } from '@tests/selectors/settings.selectors'; + +import { Item } from '@app/ui/components/item/item'; +import { ItemLayout } from '@app/ui/components/item/item.layout'; +import { Spinner } from '@app/ui/components/spinner'; + +interface AccountListItemLayoutProps extends ComponentProps<'div'> { + accountAddresses: ReactNode; + accountName: ReactNode; + avatar: ReactNode; + balanceLabel: ReactNode; + index: number; + isLoading: boolean; + isSelected: boolean; + onSelectAccount(): void; +} +export function AccountListItemLayout(props: AccountListItemLayoutProps) { + const { + accountAddresses, + accountName, + avatar, + balanceLabel, + index, + isLoading, + isSelected, + onSelectAccount, + } = props; + + return ( + + + ) : ( + balanceLabel + ) + } + captionLeft={accountAddresses} + /> + + ); +} 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 8652feb9670..80e06aa8328 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 @@ -34,7 +34,6 @@ export function Brc20TokenAssetList(props: { brc20Tokens?: Brc20Token[] }) { key={token.tick} displayNotEnoughBalance={!hasPositiveBtcBalanceForFees} token={token} - isPressable={hasPositiveBtcBalanceForFees} onClick={hasPositiveBtcBalanceForFees ? () => 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 750b729abc4..7aa511e1a2c 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,20 +1,21 @@ +import { styled } from 'leather-styles/jsx'; + import { createMoney } from '@shared/models/money.model'; +import { isDefined } from '@shared/utils'; import { formatBalance } from '@app/common/format-balance'; 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 { Item } from '@app/ui/components/item/item'; +import { ItemLayout } from '@app/ui/components/item/item.layout'; import { BasicTooltip } from '@app/ui/components/tooltip/basic-tooltip'; interface Brc20TokenAssetItemLayoutProps { token: Brc20Token; - isPressable?: boolean; onClick?(): void; displayNotEnoughBalance?: boolean; } export function Brc20TokenAssetItemLayout({ - isPressable, onClick, displayNotEnoughBalance, token, @@ -29,23 +30,22 @@ export function Brc20TokenAssetItemLayout({ label={'Not enough BTC in balance'} side="top" > - - - } - contentLeftTop={{token.tick}} - contentLeftBottom={{'BRC-20'}} - contentRightTop={ - - {formattedBalance.value} - - } - /> - + + } + titleLeft={token.tick} + captionLeft={'BRC-20'} + titleRight={ + + + {formattedBalance.value} + + + } + /> ); 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 81eb70a1ecc..4e790a4e35f 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 @@ -18,7 +18,6 @@ export function CryptoAssetListItem(props: CryptoAssetListItemProps) { } - isPressable onClick={onClick} /> ); 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 d40c64ede1f..0605e67c3bf 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 @@ -29,7 +29,6 @@ export function CryptoAssetList({ cryptoAssetBalances, onItemClick }: CryptoAsse assetBalance={balance} icon={} onClick={() => onItemClick(balance)} - isPressable /> )} 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 8d7389ea14f..7be8034c33e 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 @@ -13,13 +13,7 @@ export function FungibleTokenAssetItem({ assetBalance, onClick }: FungibleTokenA switch (blockchain) { case 'stacks': - return ( - - ); + return ; default: return null; } 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 d5ff281458e..57e33537b45 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,9 +1,12 @@ import { ReactNode } from 'react'; +import { styled } from 'leather-styles/jsx'; + import { AllCryptoCurrencyAssetBalances } from '@shared/models/crypto-asset-balance.model'; +import { isDefined } from '@shared/utils'; -import { Item } from '@app/ui/components/items/item'; -import { ItemDefaultLayout } from '@app/ui/components/items/item-default.layout'; +import { Item } from '@app/ui/components/item/item'; +import { ItemLayout } from '@app/ui/components/item/item.layout'; import { BasicTooltip } from '@app/ui/components/tooltip/basic-tooltip'; import { parseCryptoCurrencyAssetBalance } from './crypto-currency-asset.utils'; @@ -14,7 +17,6 @@ interface CryptoCurrencyAssetItemLayoutProps { address?: string; assetBalance: AllCryptoCurrencyAssetBalances; icon: React.ReactNode; - isPressable?: boolean; onClick?(): void; rightElement?: React.ReactNode; usdBalance?: string; @@ -25,7 +27,6 @@ export function CryptoCurrencyAssetItemLayout({ address = '', assetBalance, icon, - isPressable, onClick, rightElement, usdBalance, @@ -34,40 +35,35 @@ export function CryptoCurrencyAssetItemLayout({ parseCryptoCurrencyAssetBalance(assetBalance); return ( - - - {title}} - contentLeftBottom={{balance.symbol}} - contentRightTop={ - rightElement ? ( - rightElement - ) : ( - - - {formattedBalance.value} {additionalBalanceInfo} - - - ) - } - contentRightBottom={ - !rightElement && ( - <> - {balance.amount.toNumber() > 0 && address ? ( - {usdBalance} - ) : null} - {additionalUsdBalanceInfo} - - ) - } - /> - + + + + {formattedBalance.value} {additionalBalanceInfo} + + + ) + } + captionRight={ + !rightElement && ( + <> + {balance.amount.toNumber() > 0 && address ? usdBalance : null} + {additionalUsdBalanceInfo} + + ) + } + /> ); } 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 d47bb580151..68697d59e56 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,52 +1,52 @@ +import { styled } from 'leather-styles/jsx'; + import { StacksFungibleTokenAssetBalance } from '@shared/models/crypto-asset-balance.model'; +import { isDefined } from '@shared/utils'; import { StacksAssetAvatar } from '@app/components/crypto-assets/stacks/components/stacks-asset-avatar'; -import { Item } from '@app/ui/components/items/item'; -import { ItemDefaultLayout } from '@app/ui/components/items/item-default.layout'; +import { Item } from '@app/ui/components/item/item'; +import { ItemLayout } from '@app/ui/components/item/item.layout'; import { BasicTooltip } from '@app/ui/components/tooltip/basic-tooltip'; import { parseStacksFungibleTokenAssetBalance } from './fungible-token-asset.utils'; interface StacksFungibleTokenAssetItemLayoutProps { assetBalance: StacksFungibleTokenAssetBalance; - isPressable?: boolean; onClick?(): void; } export function StacksFungibleTokenAssetItemLayout({ assetBalance, - isPressable, onClick, }: StacksFungibleTokenAssetItemLayoutProps) { const { amount, avatar, caption, dataTestId, formattedBalance, imageCanonicalUri, title } = parseStacksFungibleTokenAssetBalance(assetBalance); return ( - - - - {title[0]} - - } - contentLeftTop={{title}} - contentLeftBottom={{caption}} - contentRightTop={ - - {formattedBalance.value} - - } - /> - + + + {title[0]} + + } + titleLeft={title} + captionLeft={caption} + titleRight={ + + + {formattedBalance.value} + + + } + /> ); } diff --git a/src/app/components/transaction-item/transaction-item.layout.tsx b/src/app/components/transaction-item/transaction-item.layout.tsx index 4248f843e04..f5a89f13b6d 100644 --- a/src/app/components/transaction-item/transaction-item.layout.tsx +++ b/src/app/components/transaction-item/transaction-item.layout.tsx @@ -1,7 +1,9 @@ import { ReactNode } from 'react'; -import { Item } from '@app/ui/components/items/item'; -import { ItemDefaultLayout } from '@app/ui/components/items/item-default.layout'; +import { styled } from 'leather-styles/jsx'; + +import { Item } from '@app/ui/components/item/item'; +import { ItemLayout } from '@app/ui/components/item/item.layout'; interface TransactionItemLayoutProps { openTxLink(): void; @@ -23,21 +25,29 @@ export function TransactionItemLayout({ txValue, }: TransactionItemLayoutProps) { return ( - - - {txTitle}} - contentLeftBottom={ - <> - {txCaption} - {txStatus && txStatus} - - } - contentRightTop={rightElement ? rightElement : {txValue}} - /> - + // TODO: Revisit if needed styles position="relative" zIndex={99} + + + + {txCaption} + + {txStatus && txStatus} + + } + titleRight={ + rightElement ? ( + rightElement + ) : ( + + {txValue} + + ) + } + /> ); } 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 d5e77122983..384fb50567c 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 @@ -4,12 +4,13 @@ import { useAccountDisplayName } from '@app/common/hooks/account/use-account-nam 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 { AcccountAddresses } from '@app/components/account/account-addresses'; +import { AccountListItemLayout } from '@app/components/account/account-list-item.layout'; +import { AccountNameLayout } from '@app/components/account/account-name'; import { useNativeSegwitSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; import { useStacksAccounts } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; import { AccountAvatarItem } from '../../../components/account/account-avatar-item'; -import { AccountNameLayout } from '../../../components/account/account-name'; interface SwitchAccountListItemProps { handleClose(): void; @@ -19,15 +20,15 @@ interface SwitchAccountListItemProps { export const SwitchAccountListItem = memo( ({ handleClose, currentAccountIndex, index }: SwitchAccountListItemProps) => { const stacksAccounts = useStacksAccounts(); - const stacksAddress = stacksAccounts[index]?.address || ''; + const stxAddress = stacksAccounts[index]?.address || ''; const bitcoinSigner = useNativeSegwitSigner(index); - const bitcoinAddress = bitcoinSigner?.(0).address || ''; + const btcAddress = bitcoinSigner?.(0).address || ''; const { isLoading, setIsLoading, setIsIdle } = useLoading( - 'SWITCH_ACCOUNTS' + stacksAddress || bitcoinAddress + 'SWITCH_ACCOUNTS' + stxAddress || btcAddress ); const { handleSwitchAccount } = useSwitchAccount(handleClose); - const name = useAccountDisplayName({ address: stacksAddress, index }); + const name = useAccountDisplayName({ address: stxAddress, index }); const handleClick = async () => { setIsLoading(); @@ -39,6 +40,7 @@ export const SwitchAccountListItem = memo( return ( } accountName={{name}} avatar={ } - balanceLabel={ - - } + balanceLabel={} index={index} isLoading={isLoading} - isPressable isSelected={currentAccountIndex === index} onSelectAccount={handleClick} /> diff --git a/src/app/pages/choose-account/components/accounts.tsx b/src/app/pages/choose-account/components/accounts.tsx index f22b61e946d..793396e2589 100644 --- a/src/app/pages/choose-account/components/accounts.tsx +++ b/src/app/pages/choose-account/components/accounts.tsx @@ -12,8 +12,9 @@ import { useCreateAccount } from '@app/common/hooks/account/use-create-account'; import { useWalletType } from '@app/common/use-wallet-type'; import { slugify } from '@app/common/utils'; import { AccountTotalBalance } from '@app/components/account-total-balance'; +import { AcccountAddresses } from '@app/components/account/account-addresses'; import { AccountAvatar } from '@app/components/account/account-avatar'; -import { AccountListItemLayout } from '@app/components/account/account-list-item-layout'; +import { AccountListItemLayout } from '@app/components/account/account-list-item.layout'; import { usePressable } from '@app/components/item-hover'; import { useNativeSegwitAccountIndexAddressIndexZero } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; import { useStacksAccounts } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; @@ -45,6 +46,7 @@ const ChooseAccountItem = memo( // Padding required on outer element to prevent jumpy virtualized list } accountName={ }> {name} diff --git a/src/app/pages/receive/components/receive-item.tsx b/src/app/pages/receive/components/receive-item.tsx index a6d7e11f078..e57a9a29e4f 100644 --- a/src/app/pages/receive/components/receive-item.tsx +++ b/src/app/pages/receive/components/receive-item.tsx @@ -1,10 +1,8 @@ -import { HStack } from 'leather-styles/jsx'; - 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 { Item } from '@app/ui/components/item/item'; +import { ItemWithButtonsLayout } from '@app/ui/components/item/item-with-buttons.layout'; import { truncateMiddle } from '@app/ui/utils/truncate-middle'; interface ReceiveItemProps { @@ -26,32 +24,28 @@ export function ReceiveItem({ if (!address) return null; return ( - - {title}} - contentLeftBottom={ - {truncateMiddle(address, 6)} - } - contentRightTop={ - - + {onClickQrCode && ( + - {onClickQrCode && ( - - )} - - } - /> - + )} + + } + /> ); } 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 c55652b6522..d59114ed9d0 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 @@ -6,8 +6,9 @@ import { BitcoinSendFormValues, StacksSendFormValues } from '@shared/models/form import { useAccountDisplayName } from '@app/common/hooks/account/use-account-names'; import { AccountTotalBalance } from '@app/components/account-total-balance'; +import { AcccountAddresses } from '@app/components/account/account-addresses'; import { AccountAvatarItem } from '@app/components/account/account-avatar-item'; -import { AccountListItemLayout } from '@app/components/account/account-list-item-layout'; +import { AccountListItemLayout } from '@app/components/account/account-list-item.layout'; import { AccountNameLayout } from '@app/components/account/account-name'; import { useNativeSegwitSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; import { StacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.models'; @@ -36,6 +37,7 @@ export const AccountListItem = memo(({ index, stacksAccount, onClose }: AccountL return ( } accountName={{name}} avatar={ } index={index} isSelected={false} - isPressable isLoading={false} onSelectAccount={onSelectAccount} /> 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 2ef1aecb9df..f58520c6684 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 @@ -3,8 +3,8 @@ import { styled } from 'leather-styles/jsx'; import { formatMoneyWithoutSymbol } from '@app/common/money/format-money'; 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 { Item } from '@app/ui/components/item/item'; +import { ItemLayout } from '@app/ui/components/item/item.layout'; import { useAlexSdkBalanceAsFiat } from '../../hooks/use-alex-sdk-fiat-price'; import { SwapAsset } from '../../hooks/use-swap-form'; @@ -22,17 +22,14 @@ export function SwapAssetItem({ asset, dataTestId, onClick }: SwapAssetItemProps const displayName = asset.displayName ?? ftMetadataName; return ( - - - } - contentLeftTop={{displayName}} - contentLeftBottom={{asset.name}} - contentRightTop={{formatMoneyWithoutSymbol(asset.balance)}} - contentRightBottom={{balanceAsFiat}} - /> - + + } + titleLeft={displayName} + captionLeft={asset.name} + titleRight={formatMoneyWithoutSymbol(asset.balance)} + captionRight={balanceAsFiat} + /> ); } diff --git a/src/app/ui/components/item/item-with-buttons.layout.tsx b/src/app/ui/components/item/item-with-buttons.layout.tsx new file mode 100644 index 00000000000..08770e617e6 --- /dev/null +++ b/src/app/ui/components/item/item-with-buttons.layout.tsx @@ -0,0 +1,45 @@ +import { ReactNode } from 'react'; + +import { Flex, HStack, Stack, styled } from 'leather-styles/jsx'; + +import { Flag } from '@app/components/layout/flag'; + +import { itemCaptionStyles } from './item'; + +interface ItemWithButtonsLayoutProps { + buttons: ReactNode; + caption: string; + flagImg: ReactNode; + title: string; +} +export function ItemWithButtonsLayout({ + buttons, + caption, + flagImg, + title, +}: ItemWithButtonsLayoutProps) { + return ( + + + + + {title} + + + {caption} + + + + {buttons} + + + + ); +} diff --git a/src/app/ui/components/item/item.layout.tsx b/src/app/ui/components/item/item.layout.tsx new file mode 100644 index 00000000000..8d1df8b34ae --- /dev/null +++ b/src/app/ui/components/item/item.layout.tsx @@ -0,0 +1,81 @@ +import { ReactNode, isValidElement } from 'react'; + +import { Flex, HStack, Stack, styled } from 'leather-styles/jsx'; + +import { Flag } from '@app/components/layout/flag'; + +import { CheckmarkIcon } from '../icons/checkmark-icon'; +import { ChevronUpIcon } from '../icons/chevron-up-icon'; +import { itemCaptionStyles, itemChevronStyles } from './item'; + +interface ItemLayoutProps { + captionLeft: ReactNode; + captionRight?: ReactNode; + flagImg: ReactNode; + isDisabled?: boolean; + isSelected?: boolean; + showChevron?: boolean; + titleLeft: ReactNode; + titleRight: ReactNode; +} +export function ItemLayout({ + captionLeft, + captionRight, + flagImg, + isSelected, + showChevron, + titleLeft, + titleRight, +}: ItemLayoutProps) { + return ( + + + + + {isValidElement(titleLeft) ? ( + titleLeft + ) : ( + + {titleLeft} + + )} + {isSelected && } + + {isValidElement(captionLeft) ? ( + captionLeft + ) : ( + + {captionLeft} + + )} + + + + {isValidElement(titleRight) ? ( + titleRight + ) : ( + + {titleRight} + + )} + {isValidElement(captionRight) ? ( + captionRight + ) : ( + + {captionRight} + + )} + + {showChevron && } + + + + ); +} diff --git a/src/app/ui/components/item/item.stories.tsx b/src/app/ui/components/item/item.stories.tsx new file mode 100644 index 00000000000..338005a49c2 --- /dev/null +++ b/src/app/ui/components/item/item.stories.tsx @@ -0,0 +1,74 @@ +import { Meta, StoryObj } from '@storybook/react'; + +import { Button } from '../button/button'; +import { BtcIcon } from '../icons/btc-icon'; +import { CopyIcon } from '../icons/copy-icon'; +import { QrCodeIcon } from '../icons/qr-code-icon'; +import { Item } from './item'; +import { ItemWithButtonsLayout } from './item-with-buttons.layout'; +import { ItemLayout } from './item.layout'; + +const meta: Meta = { + component: Item.Root, + tags: ['autodocs'], + title: 'Item', +}; + +export default meta; +type Story = StoryObj; + +export const ItemInteractive: Story = { + render: () => ( + + <> + + ), +}; + +export const Default: Story = { + render: () => ( + + } + titleLeft="Label" + captionLeft="Caption" + titleRight="1,000.00 ABC" + captionRight="$1,000.00" + /> + + ), +}; + +export const Disabled: Story = { + render: () => ( + + } + titleLeft="Label" + captionLeft="Caption" + titleRight="1,000.00 ABC" + captionRight="$1,000.00" + /> + + ), +}; + +export const WithButton: Story = { + render: () => ( + + } + title="Label" + caption="Caption" + buttons={ + <> + + + + } + /> + + ), +}; diff --git a/src/app/ui/components/item/item.tsx b/src/app/ui/components/item/item.tsx new file mode 100644 index 00000000000..ec6c9f5952a --- /dev/null +++ b/src/app/ui/components/item/item.tsx @@ -0,0 +1,45 @@ +import { forwardRef } from 'react'; + +import { css } from 'leather-styles/css'; +import { styled } from 'leather-styles/jsx'; +import { type ItemVariantProps, item as itemRecipe } from 'leather-styles/recipes'; +import { HTMLStyledProps } from 'leather-styles/types'; + +import { HasChildren } from '@app/common/has-children'; + +export const itemCaptionStyles = css({ + _groupDisabled: { color: 'accent.non-interactive' }, + color: 'accent.text-subdued', +}); + +export const itemChevronStyles = css({ + _groupDisabled: { color: 'accent.non-interactive' }, + color: 'accent.action-primary-default', +}); + +const ItemBase = forwardRef & ItemVariantProps>( + (props, ref) => +); + +const ItemInteractive = forwardRef & ItemVariantProps>( + (props, ref) => ( + + ) +); + +interface RootProps { + isDisabled?: boolean; + isInteractive?: boolean; + onClickItem?(): void; +} +function Root({ children, isDisabled, isInteractive, onClickItem }: RootProps & HasChildren) { + return isInteractive ? ( + + {children} + + ) : ( + {children} + ); +} + +export const Item = { Root }; diff --git a/src/app/ui/components/items/item-default.layout.tsx b/src/app/ui/components/items/item-default.layout.tsx deleted file mode 100644 index ad9b7b0e7f5..00000000000 --- a/src/app/ui/components/items/item-default.layout.tsx +++ /dev/null @@ -1,59 +0,0 @@ -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 deleted file mode 100644 index 7900957c59e..00000000000 --- a/src/app/ui/components/items/item-with-buttons.layout.tsx +++ /dev/null @@ -1,45 +0,0 @@ -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 deleted file mode 100644 index f739c1ce5b9..00000000000 --- a/src/app/ui/components/items/item.stories.tsx +++ /dev/null @@ -1,147 +0,0 @@ -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 deleted file mode 100644 index eec33764e1e..00000000000 --- a/src/app/ui/components/items/item.tsx +++ /dev/null @@ -1,137 +0,0 @@ -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/typography/caption.tsx b/src/app/ui/components/typography/caption.tsx index 96eb6c55b1b..d87df4d218e 100644 --- a/src/app/ui/components/typography/caption.tsx +++ b/src/app/ui/components/typography/caption.tsx @@ -1,9 +1,17 @@ import { forwardRef } from 'react'; -import { BoxProps, styled } from 'leather-styles/jsx'; +import { HTMLStyledProps, styled } from 'leather-styles/jsx'; -export const Caption = forwardRef(({ children, ...props }, ref) => ( - - {children} - -)); +export const Caption = forwardRef>( + ({ children, ...props }, ref) => ( + + {children} + + ) +); diff --git a/src/app/ui/components/typography/title.tsx b/src/app/ui/components/typography/title.tsx index 0129f184d97..744ace4763e 100644 --- a/src/app/ui/components/typography/title.tsx +++ b/src/app/ui/components/typography/title.tsx @@ -1,15 +1,17 @@ import { forwardRef } from 'react'; -import { BoxProps, styled } from 'leather-styles/jsx'; +import { HTMLStyledProps, styled } from 'leather-styles/jsx'; -export const Title = forwardRef(({ children, ...props }, ref) => ( - - {children} - -)); +export const Title = forwardRef>( + ({ children, ...props }, ref) => ( + + {children} + + ) +); diff --git a/theme/recipes/item-recipe.ts b/theme/recipes/item-recipe.ts new file mode 100644 index 00000000000..b348e8b2e55 --- /dev/null +++ b/theme/recipes/item-recipe.ts @@ -0,0 +1,49 @@ +import { defineRecipe } from '@pandacss/dev'; + +// ts-unused-exports:disable-next-line +export const itemRecipe = defineRecipe({ + description: 'The styles for the Item component', + className: 'item', + jsx: ['Item'], + base: { + bg: 'accent.background-primary', + color: 'accent.text-primary', + display: 'flex', + height: 'auto', + minHeight: '66px', + outline: 'none', + p: 'space.03', + userSelect: 'none', + width: '100%', + }, + variants: { + interactive: { + true: { + _active: { + bg: 'accent.component-background-pressed', + }, + _disabled: { + _active: { bg: 'unset' }, + _focus: { border: 'unset' }, + _hover: { bg: 'unset' }, + color: 'accent.non-interactive', + cursor: 'not-allowed', + }, + _focus: { + _before: { + content: '""', + pos: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + border: '2px solid {colors.focus}', + }, + }, + _hover: { + bg: 'accent.component-background-hover', + }, + }, + }, + }, +});