diff --git a/src/components/amount/Amount.tsx b/src/components/amount/Amount.tsx index a1f41c45fb..05df72e53c 100644 --- a/src/components/amount/Amount.tsx +++ b/src/components/amount/Amount.tsx @@ -50,7 +50,7 @@ const ButtonContainer = styled.View` `; const ViewContainer = styled.View` - height: 100%; + flex: 1; display: flex; flex-direction: column; justify-content: space-between; @@ -65,7 +65,11 @@ const Row = styled.View` flex-direction: row; `; -const AmountText = styled(BaseText)<{bigAmount?: boolean}>` +// The entered amount is already display-sized; cap it so long values stay on +// one line instead of overflowing the hero. +const AmountText = styled(BaseText).attrs(() => ({ + maxFontSizeMultiplier: 1.3, +}))<{bigAmount?: boolean}>` font-size: ${({bigAmount}) => (bigAmount ? '35px' : '50px')}; font-weight: 500; text-align: center; @@ -93,7 +97,9 @@ const CurrencySuperScript = styled.View` top: 10px; right: -20px; `; -const CurrencyText = styled(BaseText)<{bigAmount?: boolean}>` +const CurrencyText = styled(BaseText).attrs(() => ({ + maxFontSizeMultiplier: 1.3, +}))<{bigAmount?: boolean}>` font-size: ${({bigAmount}) => (bigAmount ? '12px' : '20px')}; color: ${({theme}) => theme.colors.text}; position: absolute; diff --git a/src/components/banner/Banner.tsx b/src/components/banner/Banner.tsx index 8ef2113cd0..e968522029 100644 --- a/src/components/banner/Banner.tsx +++ b/src/components/banner/Banner.tsx @@ -25,8 +25,11 @@ import {SvgProps} from 'react-native-svg'; const BANNER_HEIGHT = 80; +// `$height` is transient on purpose: RN declares `height` as a valid native View +// prop (BaseViewConfig), so a plain `height` prop would be forwarded and applied +// as a hard height, clipping the text at large font sizes. const BannerContainer = styled.View<{ - height?: number; + $height?: number; containerBgColor?: string; }>` background-color: ${({theme: {dark}, containerBgColor}) => @@ -34,9 +37,8 @@ const BannerContainer = styled.View<{ border-radius: 10px; margin: 10px 0; padding: 10px 12px; - align-items: flex-start; justify-content: space-between; - min-height: ${({height}) => height || BANNER_HEIGHT}px; + min-height: ${({$height}) => $height || BANNER_HEIGHT}px; `; const Description = styled.View` @@ -44,7 +46,11 @@ const Description = styled.View` flex: 1; `; -const BannerRow = styled(Row)` +// Deliberately not `styled(Row)`: Row is `flex: 1` (flex-basis 0), and as the +// only child of this column it would contribute no intrinsic height, collapsing +// the container to its min-height and spilling the text out of both edges. +const BannerRow = styled.View` + flex-direction: row; align-items: center; justify-content: space-between; `; @@ -132,7 +138,7 @@ const Banner = ({ : undefined; return ( - + {icon ? React.createElement(icon) : } diff --git a/src/components/button/Button.tsx b/src/components/button/Button.tsx index c6b68c6d8f..fc480f0e15 100644 --- a/src/components/button/Button.tsx +++ b/src/components/button/Button.tsx @@ -75,7 +75,12 @@ interface ButtonOptionProps { danger?: boolean; disabled?: boolean; action?: boolean; - height?: number; + /** + * Transient: RN declares `height` as a valid native View prop, so a plain + * `height` would be forwarded and applied as a hard height, beating the + * min-height below and clipping the label at large font sizes. + */ + $height?: number; children?: React.ReactNode; } @@ -162,9 +167,9 @@ const ButtonContent = styled.View` return Action; }}; border-radius: ${({borderRadius}) => borderRadius ?? BUTTON_RADIUS}px; - min-height: ${({height}) => height || BUTTON_HEIGHT}px; - padding: 4px 12px; + min-height: ${({$height}) => $height || BUTTON_HEIGHT}px; justify-content: center; + padding: 8px 12px; `; const ButtonText = styled(ButtonBaseText)` @@ -202,6 +207,7 @@ const ButtonContainerFlex = styled.View<{hasIcon: boolean}>` justify-content: ${({hasIcon}) => (hasIcon ? 'space-between' : 'center')}; padding-left: ${({hasIcon}) => (hasIcon ? 10 : 0)}px; align-items: center; + flex-shrink: 1; `; const ButtonTextContainer = styled.View` @@ -275,9 +281,14 @@ const PillContent = styled.View` }}; border-radius: ${PILL_RADIUS}px; padding: 8px 15px; + flex-shrink: 1; `; -const PillText = styled(BaseText)` +// Pills are compact chips (often in fixed-height nav headers), so their label +// is capped rather than left to scale unbounded. +const PillText = styled(BaseText).attrs(() => ({ + maxFontSizeMultiplier: 1.4, +}))` font-size: 15px; font-weight: 400; line-height: 22.03px; @@ -432,7 +443,7 @@ const Button: React.FC> = props => { activeOpacity={disabled ? 1 : ActiveOpacity} testID={testID || 'button'}> (dark ? Action : 'transparent')}; background: ${({theme: {dark}}) => (dark ? '#2240C440' : LightBlue)}; @@ -69,13 +71,19 @@ export const SearchFilterLabelContainer = styled.View` margin-left: 15px; margin-right: 15px; flex: 1 1 auto; + flex-shrink: 1; `; -export const SearchFilterLabel = styled(BaseText)` +// Compact filter chip that shares a fixed-height row with the search input. +export const SearchFilterLabel = styled(BaseText).attrs(() => ({ + maxFontSizeMultiplier: 1.4, + numberOfLines: 2, +}))` color: ${({theme: {dark}}) => (dark ? White : Action)}; font-size: 12px; font-weight: 400; min-width: 70px; + flex-shrink: 1; `; export const SearchFilterIconContainer = styled.View` @@ -462,7 +470,7 @@ const SearchComponent = ({ style={ selectedChainFilterOption && currencyInfo ? {marginLeft: 5} : null }> - + {selectedChainFilterOption && currencyInfo ? currencyInfo.name : t('All Networks')} diff --git a/src/components/charts/ChartAxisLabel.tsx b/src/components/charts/ChartAxisLabel.tsx index e6cdfbcb31..a07355b64d 100644 --- a/src/components/charts/ChartAxisLabel.tsx +++ b/src/components/charts/ChartAxisLabel.tsx @@ -197,6 +197,9 @@ const ChartAxisLabel = ({ }); }}> ` const TimeframeRow = styled.View` flex-direction: row; + flex-wrap: wrap; + row-gap: 8px; justify-content: space-between; align-self: center; width: 100%; @@ -44,7 +46,7 @@ const TimeframeHitSlop = {top: 10, bottom: 10, left: 10, right: 10} as const; type TimeframeSelectorStyledProps = {$active: boolean}; const TimeframePill = styled(TouchableOpacity)` - height: 34px; + min-height: 34px; min-width: 44px; padding: 0 12px; border-radius: 18px; diff --git a/src/components/custom-tab-bar/CustomTabBar.tsx b/src/components/custom-tab-bar/CustomTabBar.tsx index 91fab050da..e4fd88ffce 100644 --- a/src/components/custom-tab-bar/CustomTabBar.tsx +++ b/src/components/custom-tab-bar/CustomTabBar.tsx @@ -11,16 +11,19 @@ import { White, } from '../../styles/colors'; import {BaseText} from '../styled/Text'; +import {WIDTH} from '../styled/Containers'; const gutter = 5; const tabWidth = 150; const TabBarContainer = styled.View<{darkMode: boolean; totalWidth: number}>` flex-direction: row; + align-items: center; align-self: center; border-radius: 50px; width: ${({totalWidth}) => totalWidth}px; - height: 56px; + max-width: ${WIDTH - gutter * 4}px; + min-height: 56px; background-color: ${({darkMode}) => (darkMode ? LightBlack : NeutralSlate)}; `; @@ -34,7 +37,7 @@ const TabButton = styled(TouchableOpacity)<{ justify-content: center; gap: 5px; width: ${({tabWidth}) => tabWidth}px; - height: 44px; + min-height: 44px; padding-vertical: 10px; border-radius: 50px; margin: ${gutter}px; @@ -42,12 +45,19 @@ const TabButton = styled(TouchableOpacity)<{ background-color: ${({isFocused}) => (isFocused ? Action : 'transparent')}; `; -const TabLabel = styled(BaseText)<{isFocused: boolean}>` +// The pill is a fixed-width control, so the label is capped rather than left to +// scale past the width it can occupy. +const TabLabel = styled(BaseText).attrs(() => ({ + numberOfLines: 2, + maxFontSizeMultiplier: 1.6, +}))<{isFocused: boolean}>` color: ${({theme: {dark}, isFocused}) => dark ? NeutralSlate : isFocused ? NeutralSlate : SlateDark}; font-size: 16px; text-transform: none; font-weight: 500; + flex-shrink: 1; + text-align: center; `; const IconContainer = styled.View` diff --git a/src/components/form/BoxInput.tsx b/src/components/form/BoxInput.tsx index e91001afaf..eb0d880676 100644 --- a/src/components/form/BoxInput.tsx +++ b/src/components/form/BoxInput.tsx @@ -89,13 +89,15 @@ const Separator = styled.View` border-right-color: ${({theme}) => (theme.dark ? '#45484E' : LightBlue)}; border-right-width: 1px; border-style: solid; - height: ${SEPARATOR_HEIGHT}px; + align-self: stretch; + min-height: ${SEPARATOR_HEIGHT}px; + margin: 9px 0; `; const Input = styled(TextInputMask)` background-color: transparent; color: ${({theme}) => theme.colors.text}; - height: ${INPUT_HEIGHT}px; + min-height: ${INPUT_HEIGHT}px; padding: 10px; flex: 1 1 auto; font-weight: 500; @@ -133,7 +135,7 @@ export const IconContainer = styled(TouchableOpacity).attrs(() => ({ activeOpacity: ActiveOpacity, }))` align-items: center; - height: ${INPUT_HEIGHT}px; + min-height: ${INPUT_HEIGHT}px; min-width: ${INPUT_HEIGHT}px; justify-content: center; `; diff --git a/src/components/list/AccountListRow.tsx b/src/components/list/AccountListRow.tsx index f032da6053..4a574dcb70 100644 --- a/src/components/list/AccountListRow.tsx +++ b/src/components/list/AccountListRow.tsx @@ -22,6 +22,7 @@ import {SearchableItem} from '../chain-search/ChainSearch'; import Animated, {FadeIn} from 'react-native-reanimated'; import {IsSVMChain} from '../../store/wallet/utils/currency'; import {CurrencyListIcons} from '../../constants/SupportedCurrencyOptions'; +import {useIsLargeFont} from '../../utils/hooks'; const SpinnerContainer = styled.View` display: flex; @@ -31,8 +32,9 @@ const SpinnerContainer = styled.View` padding-right: 10px; `; -const BalanceColumn = styled(Column)` - align-items: flex-end; +const BalanceColumn = styled(Column)<{stacked?: boolean}>` + align-items: ${({stacked}) => (stacked ? 'flex-start' : 'flex-end')}; + ${({stacked}) => (stacked ? 'align-self: stretch; margin-top: 6px;' : '')} `; const ListContainer = styled(Animated.View)``; @@ -113,6 +115,7 @@ const AccountListRow = ({ hideBalance, }: Props) => { const {t} = useTranslation(); + const stacked = useIsLargeFont(); const { accountName, fiatBalanceFormat, @@ -152,6 +155,7 @@ const AccountListRow = ({ {!hideIcon ? ( @@ -180,14 +184,14 @@ const AccountListRow = ({ ) : null} {isMultiNetworkSupported ? ( -
+
{accountName}
) : ( -
+
{accountName}
@@ -216,9 +220,9 @@ const AccountListRow = ({ )} {isMultiNetworkSupported ? ( fiatBalanceFormat && ( - + {!hideBalance ? ( -
+
{fiatBalanceFormat}
) : ( @@ -228,14 +232,14 @@ const AccountListRow = ({ ) ) : !isScanning ? ( cryptoBalance && ( - + {!hideBalance ? ( <> -
+
{cryptoBalance}
{showFiatBalance && ( - + {network === 'testnet' ? t('Test - No Value') : fiatBalanceFormat} diff --git a/src/components/list/AccountRow.tsx b/src/components/list/AccountRow.tsx index 7aafae49c0..857d97dfc1 100644 --- a/src/components/list/AccountRow.tsx +++ b/src/components/list/AccountRow.tsx @@ -13,15 +13,19 @@ import {CurrencyImage} from '../currency-image/CurrencyImage'; import {CurrencyListIcons} from '../../constants/SupportedCurrencyOptions'; import {AddPillContainer} from '../../navigation/wallet/screens/AddCustomToken'; import {TouchableOpacity} from '@components/base/TouchableOpacity'; +import {useIsLargeFont} from '../../utils/hooks'; -const AddressView = styled(View)` - align-items: flex-end; +const AddressView = styled(View)<{stacked?: boolean}>` + align-items: ${({stacked}) => (stacked ? 'flex-start' : 'flex-end')}; margin: 10px; `; -const RowContainer = styled(TouchableOpacity)<{selected: boolean}>` - flex-direction: row; - align-items: center; +const RowContainer = styled(TouchableOpacity)<{ + selected: boolean; + stacked?: boolean; +}>` + flex-direction: ${({stacked}) => (stacked ? 'column' : 'row')}; + align-items: ${({stacked}) => (stacked ? 'flex-start' : 'center')}; padding: 10px 4px; margin: 0 6px; justify-content: space-around; @@ -55,13 +59,15 @@ interface Props { const AccountRow = ({account, chain, selected, onPress}: Props) => { const {t} = useTranslation(); + const stacked = useIsLargeFont(); return ( -
+
{t('Account {{number}}', {number: account.accountNumber})}
{account.network !== 'livenet' && ( @@ -71,7 +77,7 @@ const AccountRow = ({account, chain, selected, onPress}: Props) => { )}
- + diff --git a/src/components/list/AccountSettingsRow.tsx b/src/components/list/AccountSettingsRow.tsx index d79fb32a33..8d9aa90401 100644 --- a/src/components/list/AccountSettingsRow.tsx +++ b/src/components/list/AccountSettingsRow.tsx @@ -13,6 +13,7 @@ import {useTranslation} from 'react-i18next'; import {AccountRowProps} from './AccountListRow'; import {IsVMChain} from '../../store/wallet/utils/currency'; import {TouchableOpacity} from '@components/base/TouchableOpacity'; +import {useIsLargeFont} from '../../utils/hooks'; interface Props { id: string; @@ -25,9 +26,9 @@ const HiddenColumn = styled(Column)` align-items: flex-end; `; -const AccountSettingsContainer = styled(TouchableOpacity)` - flex-direction: row; - align-items: center; +const AccountSettingsContainer = styled(TouchableOpacity)<{stacked?: boolean}>` + flex-direction: ${({stacked}) => (stacked ? 'column' : 'row')}; + align-items: ${({stacked}) => (stacked ? 'flex-start' : 'center')}; display: flex; padding: 8px 0px; gap: 8px; @@ -37,6 +38,7 @@ const AccountSettingsRow = ({accountItem, accountInfo, onPress}: Props) => { const {accountName, receiveAddress, wallets, isMultiNetworkSupported} = accountItem; const {t} = useTranslation(); + const stacked = useIsLargeFont(); const hideAccount = accountInfo?.[accountItem.receiveAddress]?.hideAccount; const isHidden = IsVMChain(wallets[0].chain) @@ -46,6 +48,7 @@ const AccountSettingsRow = ({accountItem, accountInfo, onPress}: Props) => { return ( onPress()}> {isMultiNetworkSupported ? ( @@ -59,7 +62,7 @@ const AccountSettingsRow = ({accountItem, accountInfo, onPress}: Props) => { )} -
+
{accountName || t('[Account Name]')}
diff --git a/src/components/list/AltCurrenciesRow.tsx b/src/components/list/AltCurrenciesRow.tsx index 076422ef2d..bdf391d3b3 100644 --- a/src/components/list/AltCurrenciesRow.tsx +++ b/src/components/list/AltCurrenciesRow.tsx @@ -44,8 +44,8 @@ const AltCurrencyIsoCodeContainer = styled.View<{selected?: boolean}>` css` background: ${({theme: {dark}}) => (dark ? SlateDark : Slate30)}; `}; - width: 60px; - height: 40px; + min-width: 60px; + min-height: 40px; border-radius: 27.5px; align-items: center; justify-content: center; diff --git a/src/components/list/AssetsByChainRow.tsx b/src/components/list/AssetsByChainRow.tsx index f5cdaa7e62..2e6c625182 100644 --- a/src/components/list/AssetsByChainRow.tsx +++ b/src/components/list/AssetsByChainRow.tsx @@ -19,6 +19,7 @@ import ChevronUpSvgDark from '../../../assets/img/chevron-up-darkmode.svg'; import {useTheme} from 'styled-components/native'; import {setLocalAssetsDropdown} from '../../store/app/app.actions'; import {useAppDispatch, useAppSelector} from '../../utils/hooks'; +import {useIsLargeFont} from '../../utils/hooks'; const CurrencyImageContainer = styled.View` height: 30px; @@ -62,6 +63,7 @@ const AssetsByChainRow = ({ showChainAssetsByDefault = false, showNetworkHeader = true, }: Props) => { + const stacked = useIsLargeFont(); const {chain, chainName, fiatBalanceFormat, chainAssetsList, chainImg} = accountItem; const dispatch = useAppDispatch(); @@ -95,19 +97,24 @@ const AssetsByChainRow = ({ -
+
{chainName}
- + {!hideBalance ? ( -
+
{fiatBalanceFormat}
) : ( diff --git a/src/components/list/KeyWalletsRow.tsx b/src/components/list/KeyWalletsRow.tsx index 19da2baa26..1e21b7113e 100644 --- a/src/components/list/KeyWalletsRow.tsx +++ b/src/components/list/KeyWalletsRow.tsx @@ -39,6 +39,7 @@ import {BitpaySupportedCoins} from '../../constants/currencies'; import {SearchableItem} from '../chain-search/ChainSearch'; import {TouchableOpacity} from '@components/base/TouchableOpacity'; import {SupportedTransactionCurrencies} from '../../store/wallet/effects/paypro/paypro'; +import {useIsLargeFont} from '../../utils/hooks'; interface KeyWalletsRowContainerProps { isLast?: boolean; @@ -104,9 +105,9 @@ const ChainAssetsContainer = styled(Row)` flex-direction: row; `; -const AccountChainsContainer = styled(TouchableOpacity)` - flex-direction: row; - align-items: center; +const AccountChainsContainer = styled(TouchableOpacity)<{stacked?: boolean}>` + flex-direction: ${({stacked}) => (stacked ? 'column' : 'row')}; + align-items: ${({stacked}) => (stacked ? 'flex-start' : 'center')}; margin: 0px; border-bottom-color: ${({theme: {dark}}) => (dark ? LightBlack : LightBlue)}; gap: 11px; @@ -184,6 +185,7 @@ const KeyWalletsRow = ({ supportedTransactionCurrencies, }: KeyWalletProps) => { const {t} = useTranslation(); + const stacked = useIsLargeFont(); const {keys} = useAppSelector(({WALLET}) => WALLET); const [showChainAssets, setShowChainAssets] = useState<{ [key: string]: boolean; @@ -229,6 +231,7 @@ const KeyWalletsRow = ({ key={account.id} isLast={key?.mergedUtxoAndEvmAccounts.length === index + 1}> onHide(evmAccount?.receiveAddress)}> -
+
{evmAccount?.accountName}
- + @@ -288,7 +295,9 @@ const KeyWalletsRow = ({ -
+
{chainName}
@@ -345,6 +354,7 @@ const KeyWalletsRow = ({ {!prev || (prev.chain !== wallet.chain && ( -
+
{BitpaySupportedCoins[ // @ts-ignore wallet?.currencyAbbreviation?.toLowerCase() ]?.name ?? ''}
- + {theme.dark ? ( diff --git a/src/components/list/TransactionProposalRow.tsx b/src/components/list/TransactionProposalRow.tsx index 9f47a8ec72..c82e1c5106 100644 --- a/src/components/list/TransactionProposalRow.tsx +++ b/src/components/list/TransactionProposalRow.tsx @@ -7,11 +7,15 @@ import {GetContactName} from '../../store/wallet/effects/transactions/transactio import {ContactRowProps} from './ContactRow'; import {TouchableOpacity} from '@components/base/TouchableOpacity'; import {Dimensions} from 'react-native'; +import {useIsLargeFont} from '../../utils/hooks'; const {width} = Dimensions.get('window'); -const TransactionContainer = styled(TouchableOpacity)<{withCheckBox?: boolean}>` - flex-direction: row; +const TransactionContainer = styled(TouchableOpacity)<{ + withCheckBox?: boolean; + stacked?: boolean; +}>` + flex-direction: ${({stacked}) => (stacked ? 'column' : 'row')}; padding: 10px ${ScreenGutter}; justify-content: space-between; width: ${width - 50}px; @@ -22,27 +26,28 @@ const IconContainer = styled.View` margin-right: 8px; `; -const Description = styled(BaseText)` +const Description = styled(BaseText)<{stacked?: boolean}>` overflow: hidden; font-size: 16px; - max-width: 150px; + max-width: ${({stacked}) => (stacked ? '100%' : '150px')}; `; -const Creator = styled(ListItemSubText)` +const Creator = styled(ListItemSubText)<{stacked?: boolean}>` overflow: hidden; - max-width: 150px; + max-width: ${({stacked}) => (stacked ? '100%' : '150px')}; `; -const TailContainer = styled.View` - margin-left: auto; +const TailContainer = styled.View<{stacked?: boolean}>` + ${({stacked}) => + stacked ? 'align-self: stretch; margin-top: 6px;' : 'margin-left: auto;'} display: flex; justify-content: center; `; const HeadContainer = styled.View``; -const Value = styled(BaseText)` - text-align: right; +const Value = styled(BaseText)<{stacked?: boolean}>` + text-align: ${({stacked}) => (stacked ? 'left' : 'right')}; font-weight: 700; font-size: 16px; `; @@ -95,28 +100,38 @@ const TransactionProposalRow = ({ } } + const stacked = useIsLargeFont(); return ( {icon && !hideIcon && {icon}} {message ? message : label} {creator && ( - + {t('Created by ', {creator})} )} - - {value && {value}} - {time && {time}} + + {value && {value}} + {time && ( + + {time} + + )} ); diff --git a/src/components/list/TransactionRow.tsx b/src/components/list/TransactionRow.tsx index caf61f927b..3993b77650 100644 --- a/src/components/list/TransactionRow.tsx +++ b/src/components/list/TransactionRow.tsx @@ -8,12 +8,13 @@ import {CurrencyListIcons} from '../../constants/SupportedCurrencyOptions'; import {CurrencyImage} from '../currency-image/CurrencyImage'; export const TRANSACTION_ROW_HEIGHT = 75; import {TouchableOpacity} from '@components/base/TouchableOpacity'; +import {useIsLargeFont} from '../../utils/hooks'; -const TransactionContainer = styled(TouchableOpacity)` - flex-direction: row; +const TransactionContainer = styled(TouchableOpacity)<{stacked?: boolean}>` + flex-direction: ${({stacked}) => (stacked ? 'column' : 'row')}; padding: ${ScreenGutter}; - align-items: center; - height: ${TRANSACTION_ROW_HEIGHT}px; + align-items: ${({stacked}) => (stacked ? 'flex-start' : 'center')}; + min-height: ${TRANSACTION_ROW_HEIGHT}px; `; const IconContainer = styled.View` @@ -36,11 +37,14 @@ const Description = styled(BaseText)` font-size: 16px; `; -const TailContainer = styled.View``; +const TailContainer = styled.View<{stacked?: boolean}>` + flex-shrink: 0; + ${({stacked}) => (stacked ? 'align-self: stretch; margin-top: 6px;' : '')} +`; -const Value = styled(BaseText)` +const Value = styled(BaseText)<{stacked?: boolean}>` color: ${({theme}) => theme.colors.text}; - text-align: right; + text-align: ${({stacked}) => (stacked ? 'left' : 'right')}; font-weight: 700; font-size: 16px; `; @@ -78,8 +82,9 @@ const TransactionRow = ({ chain, onPressTransaction, }: Props) => { + const stacked = useIsLargeFont(); return ( - + {iconURI ? ( - + {description} {details && ( @@ -114,10 +121,12 @@ const TransactionRow = ({ )} - - {value ? {value} : null} + + {value ? {value} : null} {time ? ( - {time} + + {time} + ) : null} diff --git a/src/components/list/WalletRow.tsx b/src/components/list/WalletRow.tsx index fe26950e94..5794bb7f9f 100644 --- a/src/components/list/WalletRow.tsx +++ b/src/components/list/WalletRow.tsx @@ -29,6 +29,7 @@ import {IsERCToken, IsVMChain} from '../../store/wallet/utils/currency'; import GasTokenSvg from '../../../assets/img/gas-token.svg'; import {getCurrencyCodeFromCoinAndChain} from '../../navigation/bitpay-id/utils/bitpay-id-utils'; import {SupportedTransactionCurrencies} from '../../store/wallet/effects/paypro/paypro'; +import {useIsLargeFont} from '../../utils/hooks'; const SpinnerContainer = styled.View` display: flex; @@ -38,8 +39,9 @@ const SpinnerContainer = styled.View` padding-right: 10px; `; -const BalanceColumn = styled(Column)` - align-items: flex-end; +const BalanceColumn = styled(Column)<{stacked?: boolean}>` + align-items: ${({stacked}) => (stacked ? 'flex-start' : 'flex-end')}; + ${({stacked}) => (stacked ? 'align-self: stretch; margin-top: 6px;' : '')} `; const NestedArrowContainer = styled.View` @@ -180,6 +182,7 @@ const WalletRow = ({ supportedTransactionCurrencies, }: Props) => { const {t} = useTranslation(); + const stacked = useIsLargeFont(); const { currencyName, currencyAbbreviation, @@ -223,7 +226,8 @@ const WalletRow = ({ isDisabled={ supportedTransactionCurrencies && (isDisabled || !isAvailable) } - disabled={supportedTransactionCurrencies && (isDisabled || !isAvailable)}> + disabled={supportedTransactionCurrencies && (isDisabled || !isAvailable)} + stacked={stacked}> {isToken && ( @@ -236,7 +240,7 @@ const WalletRow = ({ ) : null} -
+
{walletName || currencyName}
@@ -276,14 +280,14 @@ const WalletRow = ({
{!isScanning ? ( - + {!hideBalance ? ( <> -
+
{cryptoBalance}
{showFiatBalance && ( - + {network === 'testnet' ? t('Test - No Value') : fiatBalanceFormat} diff --git a/src/components/list/WalletSettingsRow.tsx b/src/components/list/WalletSettingsRow.tsx index 346d229ee4..d4d14a0b34 100644 --- a/src/components/list/WalletSettingsRow.tsx +++ b/src/components/list/WalletSettingsRow.tsx @@ -10,6 +10,7 @@ import { } from '../styled/Containers'; import {useTranslation} from 'react-i18next'; import {TouchableOpacity} from '@components/base/TouchableOpacity'; +import {useIsLargeFont} from '../../utils/hooks'; export interface WalletSettingsRowProps { img: string | ((props: any) => ReactElement); @@ -26,9 +27,9 @@ const HiddenColumn = styled(Column)` align-items: flex-end; `; -const WalletSettingsContainer = styled(TouchableOpacity)` - flex-direction: row; - align-items: center; +const WalletSettingsContainer = styled(TouchableOpacity)<{stacked?: boolean}>` + flex-direction: ${({stacked}) => (stacked ? 'column' : 'row')}; + align-items: ${({stacked}) => (stacked ? 'flex-start' : 'center')}; display: flex; padding: 8px 0px; gap: 8px; @@ -45,14 +46,16 @@ const WalletSettingsRow = ({ onPress, }: WalletSettingsRowProps) => { const {t} = useTranslation(); + const stacked = useIsLargeFont(); return ( onPress()} + stacked={stacked} activeOpacity={ActiveOpacity}> -
+
{walletName || currencyName} {isToken}
diff --git a/src/components/modal/bottom-notification/BottomNotification.tsx b/src/components/modal/bottom-notification/BottomNotification.tsx index 8131d15fac..e704a38491 100644 --- a/src/components/modal/bottom-notification/BottomNotification.tsx +++ b/src/components/modal/bottom-notification/BottomNotification.tsx @@ -1,11 +1,9 @@ import React, {ReactNode, useEffect, useMemo, useCallback} from 'react'; -import {useWindowDimensions} from 'react-native'; -import {ScrollView} from 'react-native-gesture-handler'; -import {BottomSheetScrollView} from '@gorhom/bottom-sheet'; +import {Platform, useWindowDimensions} from 'react-native'; import {useSafeAreaInsets} from 'react-native-safe-area-context'; import SheetModal from '../base/sheet/SheetModal'; import {BaseText, fontFamily, H4} from '../../styled/Text'; -import styled from 'styled-components/native'; +import styled, {css} from 'styled-components/native'; import {useDispatch, useSelector} from 'react-redux'; import {AppActions} from '../../../store/app'; import {RootState} from '../../../store'; @@ -61,11 +59,11 @@ const notificationType = { wait: , }; -const BottomNotificationContainer = styled(ScrollView)` +const BottomNotificationContainer = styled.View` background: ${({theme: {dark}}) => (dark ? LightBlack : White)}; + padding: 25px; border-top-left-radius: 10px; border-top-right-radius: 10px; - flex-grow: 0; `; const Row = styled.View` @@ -77,7 +75,9 @@ const ImageContainer = styled.View` margin-right: 10px; `; -const Title = styled(H4)` +// Title + message scroll together so the action buttons stay on screen when +// the text is tall. +const NotificationBody = styled.ScrollView` flex-shrink: 1; `; @@ -85,6 +85,10 @@ const MessageContainer = styled.View` margin: 15px 0 20px 0; `; +const NotificationTitle = styled(H4)` + flex-shrink: 1; +`; + export const BottomNotificationHr = styled.View` border-bottom-color: ${({theme: {dark}}) => (dark ? SlateDark : '#ebebeb')}; border-bottom-width: 1px; @@ -92,14 +96,20 @@ export const BottomNotificationHr = styled.View` `; const CtaContainer = styled.View` - flex-direction: column; + flex-direction: row; + flex-wrap: wrap; + gap: 12px; + justify-content: space-between; + ${({platform}: {platform: string}) => + platform === 'ios' && + css` + margin-bottom: 10px; + `} `; const ActionButton = styled(TouchableOpacity)` - min-height: 48px; - width: 100%; - justify-content: center; - padding: 10px 0; + min-height: 30px; + min-width: 60px; `; export const BottomNotificationCta = styled(BaseText)` @@ -218,25 +228,21 @@ const BottomNotification = React.memo(() => { isVisible={isVisible} onBackdropPress={handleBackdropPress}> - - {iconElement} - {title} - - {message ? ( - - {message} - - ) : null} - {message2 ? message2 : null} + style={{maxHeight: height - insets.top - 100}}> + + + {iconElement} + {title} + + {message ? ( + + {message} + + ) : null} + {message2 ? message2 : null} + - {actionButtons} + {actionButtons} ); diff --git a/src/components/modal/import-ledger-wallet/pair-device/LearnHow.tsx b/src/components/modal/import-ledger-wallet/pair-device/LearnHow.tsx index e6b63cbe8a..194c49bc89 100644 --- a/src/components/modal/import-ledger-wallet/pair-device/LearnHow.tsx +++ b/src/components/modal/import-ledger-wallet/pair-device/LearnHow.tsx @@ -39,8 +39,8 @@ const InstructionNumberColumn = styled.View` const InstructionNumberIcon = styled.View` background-color: ${LightBlue}; border-radius: 40px; - height: 25px; - width: 25px; + min-height: 25px; + min-width: 25px; display: flex; align-items: center; justify-content: center; diff --git a/src/components/modal/pin/PinModal.tsx b/src/components/modal/pin/PinModal.tsx index 7f8380e3f6..1bd0021850 100644 --- a/src/components/modal/pin/PinModal.tsx +++ b/src/components/modal/pin/PinModal.tsx @@ -43,11 +43,13 @@ const PinMessagesContainer = styled(Animated.View)` align-items: center; text-align: center; margin-top: 32px; + padding: 0 24px; `; const PinMessage = styled(H5)` color: ${White}; line-height: 25px; + text-align: center; `; const PinMessagesErrorContainer = styled(Animated.View)` diff --git a/src/components/modal/transact-menu/TransactMenu.tsx b/src/components/modal/transact-menu/TransactMenu.tsx index 9481f911ca..772d157210 100644 --- a/src/components/modal/transact-menu/TransactMenu.tsx +++ b/src/components/modal/transact-menu/TransactMenu.tsx @@ -90,7 +90,7 @@ const ScanButtonContainer = styled(TouchableOpacity)` align-items: center; justify-content: center; border-radius: 12px; - height: 60px; + min-height: 60px; padding-left: 11px; padding-right: 26px; margin-bottom: 30px; diff --git a/src/components/percentage/Percentage.tsx b/src/components/percentage/Percentage.tsx index 76f8f95e96..1fdea075ba 100644 --- a/src/components/percentage/Percentage.tsx +++ b/src/components/percentage/Percentage.tsx @@ -15,16 +15,20 @@ const PercentageContainer = styled(BaseText)<{ font-size: 13px; line-height: 18px; color: ${({color}) => color}; + flex-shrink: 1; `; const PercentageRow = styled.View` flex-direction: row; align-items: center; + flex-wrap: wrap; + flex-shrink: 1; `; const RangeLabel = styled(BaseText)` font-size: 13px; line-height: 18px; + flex-shrink: 1; color: ${({theme}) => getNeutralChangeColor(theme.dark)}; font-weight: 400; margin-left: 5px; diff --git a/src/components/styled/Containers.tsx b/src/components/styled/Containers.tsx index fa11695900..cbdd8fe261 100644 --- a/src/components/styled/Containers.tsx +++ b/src/components/styled/Containers.tsx @@ -18,7 +18,6 @@ import { Black, Success25, LinkBlue, - Midnight, Caution, } from '../../styles/colors'; import {BaseText} from './Text'; @@ -33,7 +32,8 @@ export const CTA_RESERVED = 104; export const ScreenGutter = '12px'; // Nav export const HeaderRightContainer = styled.View` - height: 40px; + min-height: 40px; + justify-content: center; `; export const ImageContainer = styled.View<{justifyContent?: string}>` @@ -51,18 +51,23 @@ export const ScreenContainer = styled.SafeAreaView` flex: 1; `; +// Sized for the default font; grows toward the screen edge instead of forcing +// mid-word breaks once the text no longer fits. export const TitleContainer = styled.View` - width: ${WIDTH * 0.75}px; + min-width: ${WIDTH * 0.75}px; + max-width: 100%; `; export const TextContainer = styled.View` margin-top: 10px; padding: 10px; - width: ${WIDTH * 0.9}px; + min-width: ${WIDTH * 0.9}px; + max-width: 100%; `; export const SubTextContainer = styled.View` - width: ${WIDTH * 0.8}px; + min-width: ${WIDTH * 0.8}px; + max-width: 100%; margin-top: 10px; `; @@ -131,11 +136,13 @@ interface RowContainerProps { isLast?: boolean; noBorder?: boolean; isDisabled?: boolean; + /** Stack the row's columns vertically — see useIsLargeFont. */ + stacked?: boolean; } export const RowContainer = styled(TouchableOpacity)` - flex-direction: row; - align-items: center; + flex-direction: ${({stacked}) => (stacked ? 'column' : 'row')}; + align-items: ${({stacked}) => (stacked ? 'flex-start' : 'center')}; padding: 10px 4px; margin: 0 6px; border-bottom-color: ${({theme: {dark}}) => (dark ? LightBlack : LightBlue)}; @@ -171,7 +178,6 @@ export const CurrencyImageContainer = styled.View` width: 50px; display: flex; justify-content: center; - align-self: center; border-radius: 8px; margin-right: 3px; `; @@ -209,10 +215,7 @@ export const Setting = styled(TouchableOpacity)` flex-direction: row; flex-wrap: nowrap; min-height: 58px; - padding-top: 10px; - padding-bottom: 10px; - padding-left: 15px; - padding-right: 15px; + padding: 12px 15px; `; export const SettingTitle = styled(BaseText)` @@ -230,6 +233,7 @@ export const SettingTitle = styled(BaseText)` export const SettingDescription = styled(BaseText)` color: ${({theme: {dark}}) => (dark ? White : SlateDark)}; font-size: 14px; + flex-shrink: 1; `; interface SettingIconProps { @@ -254,7 +258,8 @@ export const SettingView = styled.View` flex-direction: row; justify-content: space-between; align-items: center; - height: 58px; + min-height: 58px; + padding: 8px 0; `; export const ActionContainer = styled.View` @@ -291,7 +296,7 @@ export const AdvancedOptionsContainer = styled.View` `; export const AdvancedOptionsButton = styled(TouchableOpacity)` - height: 60px; + min-height: 60px; background-color: ${({theme}) => (theme.dark ? LightBlack : Feather)}; padding: 18px; flex-direction: row; @@ -318,7 +323,7 @@ export const ImportContainer = styled.View` `; export const ImportTextInput = styled.TextInput` - height: 80px; + min-height: 80px; color: ${({theme}) => theme.colors.text}; background: ${({theme}) => theme.colors.background}; border: 0.75px solid ${Slate}; @@ -352,7 +357,9 @@ export const OptionContainer = styled.SafeAreaView` flex: 1; `; -export const OptionListContainer = styled.View` +export const OptionListContainer = styled.ScrollView.attrs(() => ({ + contentContainerStyle: {paddingBottom: 30}, +}))` flex: 1; padding: 0 ${ScreenGutter}; margin-top: 30px; @@ -390,7 +397,7 @@ export const SearchInput = styled.TextInput` padding: 0 10px; border-right-width: 1px; border-right-color: ${({theme: {dark}}) => (dark ? '#45484E' : LightBlue)}; - height: 32px; + min-height: 32px; color: ${({theme}) => theme.colors.text}; background-color: transparent; `; @@ -403,7 +410,7 @@ export const SearchRoundContainer = styled.View` border: 1px solid ${({theme: {dark}}) => (dark ? SlateDark : Slate30)}; border-radius: 100px; align-items: center; - height: 50px; + min-height: 50px; `; export const SearchRoundInput = styled.TextInput` @@ -427,8 +434,8 @@ export const HiddenContainer = styled.View` export const CopyToClipboardContainer = styled(TouchableOpacity)` border: 1px solid #9ba3ae; border-radius: 4px; - padding: 0 10px; - height: 55px; + padding: 8px 10px; + min-height: 55px; align-items: center; flex-direction: row; `; @@ -437,7 +444,7 @@ export const CopyImgContainer = styled.View` border-right-color: ${({theme: {dark}}) => (dark ? '#46494E' : LightBlue)}; border-right-width: 1px; padding-right: 10px; - height: 25px; + min-height: 25px; justify-content: center; `; @@ -462,8 +469,9 @@ export const NoResultsDescription = styled(BaseText)` export const ProposalBadgeContainer = styled(TouchableOpacity)` background: ${Action}; border-radius: 10px; - height: 30px; - width: 30px; + min-height: 30px; + min-width: 30px; + padding: 0 6px; display: flex; align-items: center; justify-content: center; @@ -477,7 +485,7 @@ export const BadgeContainer = styled.View` padding: 4px; border-radius: 2.4px; gap: 4px; - height: 22px; + min-height: 22px; `; export const BadgeContainerTouchable = styled(TouchableOpacity)` @@ -488,7 +496,7 @@ export const BadgeContainerTouchable = styled(TouchableOpacity)` padding: 4px; border-radius: 2.4px; gap: 4px; - height: 20px; + min-height: 20px; `; export const EmptyListContainer = styled.View` diff --git a/src/components/styled/Text.tsx b/src/components/styled/Text.tsx index 11de681cd2..6e02cbfd27 100644 --- a/src/components/styled/Text.tsx +++ b/src/components/styled/Text.tsx @@ -152,9 +152,12 @@ export const ZeroHeightHeader = styled.View` height: ${StatusBar.currentHeight || 0}px; `; +// Native stack headers have a fixed height, so the title is capped rather than +// left to scale into an ellipsis. export const HeaderTitle = styled(H5).attrs(() => ({ bold: true, numberOfLines: 1, + maxFontSizeMultiplier: 1.4, }))` max-width: ${Dimensions.get('window').width - 150}px; `; @@ -187,7 +190,9 @@ export const UnderlineLink = styled(BaseText)` `; // WALLET -export const Balance = styled(BaseText)<{scale: boolean}>` +export const Balance = styled(BaseText).attrs(() => ({ + maxFontSizeMultiplier: 1.5, +}))<{scale: boolean}>` font-size: ${({scale}) => (scale ? 26 : 36)}px; font-style: normal; font-weight: 700; diff --git a/src/components/swipe-button/SwipeButton.tsx b/src/components/swipe-button/SwipeButton.tsx index 0f97adf8b4..d570c40d6a 100644 --- a/src/components/swipe-button/SwipeButton.tsx +++ b/src/components/swipe-button/SwipeButton.tsx @@ -185,6 +185,10 @@ const SwipeButton = ({ title={title} titleColor={theme.dark ? (disabled ? '#656565' : White) : White} titleStyles={{fontWeight: '500'}} + // Fixed-height rail with a full-height thumb: the label is capped so + // it stays inside the track instead of running under the thumb. + titleMaxFontScale={1.3} + titleMaxLines={2} shouldResetAfterSuccess={true} // Button resets automatically after swipe success resetAfterSuccessAnimDelay={1000} // Reset after 1s forceReset={(reset: any) => { diff --git a/src/components/virtual-keyboard/VirtualKeyboard.tsx b/src/components/virtual-keyboard/VirtualKeyboard.tsx index 57468040b7..ba97fc3f82 100644 --- a/src/components/virtual-keyboard/VirtualKeyboard.tsx +++ b/src/components/virtual-keyboard/VirtualKeyboard.tsx @@ -24,6 +24,7 @@ const RowContainer = styled.View<{isSmallScreen?: boolean}>` const CellContainer = styled.View` width: 33.333333%; + min-height: 45px; justify-content: center; align-items: center; `; @@ -86,7 +87,11 @@ export const getKeyboardSizes = ( return sizes; }; -const CellValue = styled(BaseText)<{ +// Keypad digits are already oversized and live in a fixed 3-column grid, so +// they get a capped multiplier instead of the full accessibility range. +const CellValue = styled(BaseText).attrs(() => ({ + maxFontSizeMultiplier: 1.3, +}))<{ darkModeOnly?: boolean; isSmallScreen?: boolean; context?: KeyboardSizesContext; @@ -100,7 +105,9 @@ const CellValue = styled(BaseText)<{ getKeyboardSizes(isSmallScreen, context).cellValuelineHeight}px; `; -const CellLetter = styled(BaseText)` +const CellLetter = styled(BaseText).attrs(() => ({ + maxFontSizeMultiplier: 1.3, +}))` font-size: 10px; letter-spacing: 3px; color: ${SlateDark}; @@ -287,7 +294,7 @@ const VirtualKeyboard: React.FC = ({ {showDot ? ( @@ -316,7 +323,7 @@ const VirtualKeyboard: React.FC = ({ (dark ? Slate : Slate30)}; border-radius: 8px; flex-direction: row; - height: 55px; + min-height: 55px; padding: 0 15px; margin-top: 10px; padding-left: 2px; @@ -88,7 +88,7 @@ const AddressItemText = styled(Paragraph)` `; const AddressPillContainer = styled.View` - height: 37px; + min-height: 37px; margin-right: 20px; width: 100px; `; @@ -98,7 +98,7 @@ const WalletName = styled(BaseText)` `; const AddButton = styled.View` - height: 30px; + min-height: 30px; width: 30px; background-color: ${({theme: {dark}}) => (dark ? LightBlack : Slate10)}; border-radius: 8px; diff --git a/src/navigation/bitpay-id/screens/ReceivingEnabled.tsx b/src/navigation/bitpay-id/screens/ReceivingEnabled.tsx index 18b2d635ac..4b04ab606d 100644 --- a/src/navigation/bitpay-id/screens/ReceivingEnabled.tsx +++ b/src/navigation/bitpay-id/screens/ReceivingEnabled.tsx @@ -42,7 +42,7 @@ const EmailContainer = styled.View` ${({theme}) => (theme.dark ? 0.35 : 0.05)} ); align-items: center; - height: 48px; + min-height: 48px; padding: 0 14px 0 17px; border-radius: 48px; width: 100%; diff --git a/src/navigation/card/components/CardFront.tsx b/src/navigation/card/components/CardFront.tsx index 9ac80ba8a2..ad272af0d0 100644 --- a/src/navigation/card/components/CardFront.tsx +++ b/src/navigation/card/components/CardFront.tsx @@ -41,7 +41,7 @@ const CardBalanceLabelRow = styled(BaseText)` const CardBalanceValueRow = styled.View` align-items: center; flex-direction: row; - height: 39px; + min-height: 39px; left: 0px; max-width: 100%; padding: 0 20px; diff --git a/src/navigation/coinbase/screens/CoinbaseAccount.tsx b/src/navigation/coinbase/screens/CoinbaseAccount.tsx index d08292e6d6..b6d9f37279 100644 --- a/src/navigation/coinbase/screens/CoinbaseAccount.tsx +++ b/src/navigation/coinbase/screens/CoinbaseAccount.tsx @@ -153,7 +153,7 @@ const SkeletonContainer = styled.View` const TransactionSectionHeaderContainer = styled.View` padding: ${ScreenGutter}; background-color: ${({theme: {dark}}) => (dark ? LightBlack : '#F5F6F7')}; - height: 55px; + min-height: 55px; width: 100%; display: flex; flex-direction: row; diff --git a/src/navigation/coinbase/screens/CoinbaseSettings.tsx b/src/navigation/coinbase/screens/CoinbaseSettings.tsx index a08e916446..07602c6438 100644 --- a/src/navigation/coinbase/screens/CoinbaseSettings.tsx +++ b/src/navigation/coinbase/screens/CoinbaseSettings.tsx @@ -48,7 +48,7 @@ const Detail = styled.View` flex-direction: row; justify-content: space-between; align-items: center; - height: 60px; + min-height: 60px; `; const DetailInfo = styled(TextAlign)` diff --git a/src/navigation/coinbase/screens/CoinbaseTransaction.tsx b/src/navigation/coinbase/screens/CoinbaseTransaction.tsx index 45f3c4a6a6..7ad93c00e1 100644 --- a/src/navigation/coinbase/screens/CoinbaseTransaction.tsx +++ b/src/navigation/coinbase/screens/CoinbaseTransaction.tsx @@ -62,7 +62,7 @@ const Detail = styled.View` flex-direction: row; justify-content: space-between; align-items: center; - height: 60px; + min-height: 60px; `; const DetailInfo = styled(TextAlign)` diff --git a/src/navigation/onboarding/components/OnboardingSlide.tsx b/src/navigation/onboarding/components/OnboardingSlide.tsx index 5d30e1e78c..c3f7577dd6 100644 --- a/src/navigation/onboarding/components/OnboardingSlide.tsx +++ b/src/navigation/onboarding/components/OnboardingSlide.tsx @@ -24,10 +24,16 @@ interface OnboardingSlideProps { item: OnboardingSlide; } -const SlideContainer = styled.View` +const SlideContainer = styled.ScrollView.attrs(() => ({ + contentContainerStyle: { + justifyContent: 'center', + alignItems: 'center', + flexGrow: 1, + paddingBottom: 20, + }, + showsVerticalScrollIndicator: false, +}))` background: transparent; - justify-content: center; - align-items: center; margin-top: 20px; `; diff --git a/src/navigation/services/ExternalServicesGroup.tsx b/src/navigation/services/ExternalServicesGroup.tsx index 5f67c88e50..4bc5912241 100644 --- a/src/navigation/services/ExternalServicesGroup.tsx +++ b/src/navigation/services/ExternalServicesGroup.tsx @@ -89,6 +89,8 @@ const ExternalServicesGroup = ({ {getEVMAccountName(route.params.fromWallet, allKeys) ? getEVMAccountName(route.params.fromWallet, allKeys) diff --git a/src/navigation/services/buy-crypto/styled/BuyCryptoCard.tsx b/src/navigation/services/buy-crypto/styled/BuyCryptoCard.tsx index 07f952f3ea..e771675e15 100644 --- a/src/navigation/services/buy-crypto/styled/BuyCryptoCard.tsx +++ b/src/navigation/services/buy-crypto/styled/BuyCryptoCard.tsx @@ -36,7 +36,7 @@ export const SelectedOptionContainer = styled.View` flex-direction: row; justify-content: space-between; align-items: center; - height: 40px; + min-height: 40px; padding: 0px 14px; background: ${({theme: {dark}}) => (dark ? LightBlack : NeutralSlate)}; border-radius: 12px; diff --git a/src/navigation/services/components/OfferSelectorModal.tsx b/src/navigation/services/components/OfferSelectorModal.tsx index a07242f310..8ffed627a9 100644 --- a/src/navigation/services/components/OfferSelectorModal.tsx +++ b/src/navigation/services/components/OfferSelectorModal.tsx @@ -60,7 +60,7 @@ const SafeAreaView = styled.SafeAreaView` `; const ModalHeader = styled.View` - height: 50px; + min-height: 50px; margin-right: 10px; margin-left: 10px; display: flex; @@ -114,7 +114,7 @@ const SectionTitle = styled(BaseText)` const PaymentMethodSelectorContainer = styled(TouchableOpacity)` border: 1px solid ${({theme: {dark}}) => (dark ? Slate : SlateDark)}; - height: 48px; + min-height: 48px; padding: 8px 12px; border-radius: 4px; display: flex; @@ -223,7 +223,7 @@ const BestOfferTagContainer = styled.View` const BestOfferTag = styled.View` background-color: ${({theme: {dark}}) => (dark ? '#2FCFA4' : '#cbf3e8')}; border-radius: 50px; - height: 25px; + min-height: 25px; padding: 5px 10px; `; @@ -251,7 +251,7 @@ const OfferDataInfoContainer = styled.View` align-items: center; margin-right: 10px; width: 25px; - height: 25px; + min-height: 25px; `; const OfferDataInfoLabel = styled(H7)` @@ -309,7 +309,7 @@ export const OfferSelectorModalContainer = styled.View` export const WalletSelector = styled(TouchableOpacity)` background-color: ${({theme: {dark}}) => (dark ? LightBlack : NeutralSlate)}; - height: 36px; + min-height: 36px; border-radius: 27.5px; display: flex; flex-direction: row; diff --git a/src/navigation/services/components/externalServicesAmountPills.tsx b/src/navigation/services/components/externalServicesAmountPills.tsx index 1095c6aad3..47cdbd212a 100644 --- a/src/navigation/services/components/externalServicesAmountPills.tsx +++ b/src/navigation/services/components/externalServicesAmountPills.tsx @@ -38,7 +38,7 @@ const AmountPill = styled(TouchableOpacity)<{ showMaxPill && !hideFiatPills ? '23%' : '31%'}; max-width: ${({showMaxPill, hideFiatPills}) => showMaxPill && !hideFiatPills ? '187px' : '250px'}; - height: ${({isSmallScreen}) => (isSmallScreen ? 30 : 46)}px; + min-height: ${({isSmallScreen}) => (isSmallScreen ? 30 : 46)}px; display: flex; flex-direction: row; justify-content: center; @@ -47,7 +47,13 @@ const AmountPill = styled(TouchableOpacity)<{ padding: 8px; `; -const AmountPillText = styled(BaseText)<{ +// Three pills share one row at fixed percentage widths, so the label is capped. +const AmountPillText = styled(BaseText).attrs(() => ({ + numberOfLines: 1, + adjustsFontSizeToFit: true, + minimumFontScale: 0.6, + maxFontSizeMultiplier: 1.5, +}))<{ isSelected?: boolean; isSmallScreen?: boolean; disabled?: boolean; diff --git a/src/navigation/services/components/externalServicesWalletSelector.tsx b/src/navigation/services/components/externalServicesWalletSelector.tsx index 24ff391ea1..ca89406db7 100644 --- a/src/navigation/services/components/externalServicesWalletSelector.tsx +++ b/src/navigation/services/components/externalServicesWalletSelector.tsx @@ -71,7 +71,7 @@ export const ExternalServicesWalletSelectorContainer = styled.View` export const WalletSelector = styled(TouchableOpacity)` background-color: ${({theme: {dark}}) => (dark ? LightBlack : NeutralSlate)}; - height: 36px; + min-height: 36px; border-radius: 27.5px; display: flex; flex-direction: row; diff --git a/src/navigation/services/screens/BuyAndSellRoot.tsx b/src/navigation/services/screens/BuyAndSellRoot.tsx index 93461dac6a..6f690aa2de 100644 --- a/src/navigation/services/screens/BuyAndSellRoot.tsx +++ b/src/navigation/services/screens/BuyAndSellRoot.tsx @@ -315,7 +315,10 @@ const SpinnerContainer = styled.View<{ margin-bottom: ${({addMarginBottom}) => (addMarginBottom ? '40px' : '0')}; `; -const AmountText = styled(BaseText)<{bigAmount?: boolean}>` +// Display-sized figure: it shrinks to fit rather than overflowing the hero. +const AmountText = styled(BaseText).attrs(() => ({ + maxFontSizeMultiplier: 1.3, +}))<{bigAmount?: boolean}>` font-size: ${({bigAmount}) => (bigAmount ? '35px' : '50px')}; font-weight: 500; text-align: center; @@ -342,7 +345,9 @@ const CurrencySuperScript = styled.View` top: 10px; right: -20px; `; -const CurrencyText = styled(BaseText)<{bigAmount?: boolean}>` +const CurrencyText = styled(BaseText).attrs(() => ({ + maxFontSizeMultiplier: 1.3, +}))<{bigAmount?: boolean}>` font-size: ${({bigAmount}) => (bigAmount ? '12px' : '20px')}; color: ${({theme}) => theme.colors.text}; position: absolute; @@ -368,7 +373,7 @@ const WebViewModalHeader = styled.View<{topInset: number}>` border-top-left-radius: 15px; border-top-right-radius: 15px; margin-top: ${({topInset}) => topInset}px; - height: 50px; + min-height: 50px; background-color: ${({theme: {dark}}) => (dark ? '#1a1a1a' : '#f8f8f8')}; justify-content: center; align-items: flex-start; diff --git a/src/navigation/services/screens/MoonpayBuyEmbeddedCheckout.tsx b/src/navigation/services/screens/MoonpayBuyEmbeddedCheckout.tsx index 60aeb0bdb7..4f75d0b336 100644 --- a/src/navigation/services/screens/MoonpayBuyEmbeddedCheckout.tsx +++ b/src/navigation/services/screens/MoonpayBuyEmbeddedCheckout.tsx @@ -248,7 +248,7 @@ const WebViewModalHeader = styled.View<{topInset: number}>` border-top-left-radius: 15px; border-top-right-radius: 15px; margin-top: ${({topInset}) => topInset}px; - height: 50px; + min-height: 50px; background-color: ${({theme: {dark}}) => (dark ? '#1a1a1a' : '#f8f8f8')}; justify-content: center; align-items: flex-start; @@ -1290,7 +1290,7 @@ const MoonpayBuyEmbeddedCheckout: React.FC = () => { {t('Destination')} - + ` border-top-left-radius: 15px; border-top-right-radius: 15px; margin-top: ${({topInset}) => topInset}px; - height: 50px; + min-height: 50px; background-color: ${({theme: {dark}}) => (dark ? '#1a1a1a' : '#f8f8f8')}; justify-content: center; align-items: flex-start; diff --git a/src/navigation/services/swap-crypto/components/ChangellyPoliciesModal.tsx b/src/navigation/services/swap-crypto/components/ChangellyPoliciesModal.tsx index a77ff4486c..3e968ab301 100644 --- a/src/navigation/services/swap-crypto/components/ChangellyPoliciesModal.tsx +++ b/src/navigation/services/swap-crypto/components/ChangellyPoliciesModal.tsx @@ -19,7 +19,7 @@ const ChangellyPoliciesContainer = styled.SafeAreaView` `; const ModalHeader = styled.View` - height: 50px; + min-height: 50px; margin-right: 10px; `; diff --git a/src/navigation/services/swap-crypto/components/SwapCryptoOfferSelector.tsx b/src/navigation/services/swap-crypto/components/SwapCryptoOfferSelector.tsx index a6d21329b8..7a3ce4b2b7 100644 --- a/src/navigation/services/swap-crypto/components/SwapCryptoOfferSelector.tsx +++ b/src/navigation/services/swap-crypto/components/SwapCryptoOfferSelector.tsx @@ -79,7 +79,7 @@ const OfferSelectorClickableRow = styled(TouchableOpacity)` flex-direction: row; justify-content: space-between; align-items: center; - height: 48px; + min-height: 48px; `; export const OfferSelectorContainerLeft = styled.View` diff --git a/src/navigation/services/swap-crypto/components/SwapCryptoOfferSelectorModal.tsx b/src/navigation/services/swap-crypto/components/SwapCryptoOfferSelectorModal.tsx index 1c15c32ae0..d4f217c061 100644 --- a/src/navigation/services/swap-crypto/components/SwapCryptoOfferSelectorModal.tsx +++ b/src/navigation/services/swap-crypto/components/SwapCryptoOfferSelectorModal.tsx @@ -36,7 +36,7 @@ const SafeAreaView = styled.SafeAreaView` `; const ModalHeader = styled.View` - height: 50px; + min-height: 50px; margin-right: 10px; margin-left: 10px; display: flex; @@ -182,7 +182,7 @@ const OfferDataInfoContainer = styled.View` align-items: center; margin-right: 10px; width: 25px; - height: 25px; + min-height: 25px; `; const OfferDataInfoLabel = styled(H7)` @@ -224,7 +224,7 @@ export const SwapCryptoOfferSelectorModalContainer = styled.View` export const WalletSelector = styled(TouchableOpacity)` background-color: ${({theme: {dark}}) => (dark ? LightBlack : NeutralSlate)}; - height: 36px; + min-height: 36px; border-radius: 27.5px; display: flex; flex-direction: row; diff --git a/src/navigation/services/swap-crypto/screens/SwapCryptoRoot.tsx b/src/navigation/services/swap-crypto/screens/SwapCryptoRoot.tsx index bdd170c4e0..1997899256 100644 --- a/src/navigation/services/swap-crypto/screens/SwapCryptoRoot.tsx +++ b/src/navigation/services/swap-crypto/screens/SwapCryptoRoot.tsx @@ -316,7 +316,7 @@ const OfferSelectorItemRow = styled.View` flex-direction: row; justify-content: space-between; align-items: center; - height: 48px; + min-height: 48px; `; const OfferSelectorContainer = styled.View<{isSmallScreen?: boolean}>` @@ -2905,7 +2905,7 @@ const SwapCryptoRoot: React.FC = () => { /> {fromWalletSelected.walletName ? fromWalletSelected.walletName @@ -2915,7 +2915,7 @@ const SwapCryptoRoot: React.FC = () => { ) : ( {t('Choose Wallet')} @@ -3187,7 +3187,7 @@ const SwapCryptoRoot: React.FC = () => { /> {toWalletSelected.walletName ? toWalletSelected.walletName @@ -3197,7 +3197,7 @@ const SwapCryptoRoot: React.FC = () => { ) : ( {t('Choose Crypto')} diff --git a/src/navigation/services/swap-crypto/styled/SwapCryptoCheckout.styled.tsx b/src/navigation/services/swap-crypto/styled/SwapCryptoCheckout.styled.tsx index 53567f42b1..3ccf49f35a 100644 --- a/src/navigation/services/swap-crypto/styled/SwapCryptoCheckout.styled.tsx +++ b/src/navigation/services/swap-crypto/styled/SwapCryptoCheckout.styled.tsx @@ -54,7 +54,7 @@ export const SelectedOptionContainer = styled(TouchableOpacity)` flex-direction: row; justify-content: space-between; align-items: center; - height: 40px; + min-height: 40px; padding: 0px 14px; background: ${({theme: {dark}}) => (dark ? LightBlack : NeutralSlate)}; border-radius: 19.5px; diff --git a/src/navigation/services/swap-crypto/styled/SwapCryptoRoot.styled.tsx b/src/navigation/services/swap-crypto/styled/SwapCryptoRoot.styled.tsx index 7ea6c314f0..1b1c4d1f85 100644 --- a/src/navigation/services/swap-crypto/styled/SwapCryptoRoot.styled.tsx +++ b/src/navigation/services/swap-crypto/styled/SwapCryptoRoot.styled.tsx @@ -123,7 +123,7 @@ export const SwapCardAccountChainsContainer = styled.View<{ align-items: center; justify-content: space-between; flex-shrink: 1; - height: 23px; + min-height: 23px; border-radius: 27.5px; max-width: ${({maxWidth}) => (maxWidth ? `${maxWidth}` : '250px')}; padding: ${({padding}) => padding ?? '4px 8px'}; @@ -202,7 +202,7 @@ export const SelectedOptionContainer = styled(TouchableOpacity)<{ justify-content: space-between; align-items: center; align-self: flex-end; - height: 36px; + min-height: 36px; padding: 8px; min-width: 146px; background: ${({theme: {dark}, noBackground}) => @@ -224,7 +224,7 @@ export const WalletSelector = styled(TouchableOpacity)<{ isBigScreen?: boolean; }>` background-color: ${({theme: {dark}}) => (dark ? Black : White)}; - height: 40px; + min-height: 40px; border-radius: 27.5px; display: flex; flex-direction: row; @@ -250,7 +250,12 @@ export const WalletSelectorRight = styled.View` align-items: center; `; -export const WalletSelectorName = styled.Text` +// Width-bounded chip that shares its row with the amount: the name is capped +// and allowed to wrap instead of being ellipsized away. +export const WalletSelectorName = styled.Text.attrs(() => ({ + maxFontSizeMultiplier: 1.5, +}))` + flex-shrink: 1; font-size: 16px; font-weight: 400; letter-spacing: 0px; @@ -295,7 +300,14 @@ export const DataText = styled(BaseText)` text-align: center; `; -export const AmountText = styled(BaseText)<{textLength?: number}>` +// Display-sized figure in a fixed card row: it shrinks to fit rather than +// running past the card edge. +export const AmountText = styled(BaseText).attrs(() => ({ + numberOfLines: 1, + adjustsFontSizeToFit: true, + minimumFontScale: 0.5, + maxFontSizeMultiplier: 1.5, +}))<{textLength?: number}>` font-size: ${({textLength}) => getAmountFontSize(textLength)}; font-weight: 700; letter-spacing: 0px; diff --git a/src/navigation/tabs/contacts/screens/ContactsDetails.tsx b/src/navigation/tabs/contacts/screens/ContactsDetails.tsx index 50fd613755..03b4dacba1 100644 --- a/src/navigation/tabs/contacts/screens/ContactsDetails.tsx +++ b/src/navigation/tabs/contacts/screens/ContactsDetails.tsx @@ -55,7 +55,7 @@ const Detail = styled.View` flex-direction: row; justify-content: space-between; align-items: center; - height: 60px; + min-height: 60px; `; const Notes = styled.View` diff --git a/src/navigation/tabs/home/components/AllocationSection.tsx b/src/navigation/tabs/home/components/AllocationSection.tsx index b2ad249d18..c918159f1e 100644 --- a/src/navigation/tabs/home/components/AllocationSection.tsx +++ b/src/navigation/tabs/home/components/AllocationSection.tsx @@ -13,7 +13,7 @@ import { import {BaseText} from '../../../../components/styled/Text'; import {HomeSectionTitle} from './Styled'; import ChevronRightSvg from './ChevronRightSvg'; -import {useAppSelector} from '../../../../utils/hooks'; +import {useAppSelector, useIsLargeFont} from '../../../../utils/hooks'; import type {Key, Wallet} from '../../../../store/wallet/wallet.models'; import { buildAllocationDataFromWalletRows, @@ -79,9 +79,9 @@ const Card = styled.View` padding: 14px 14px; `; -const ContentRow = styled.View` - flex-direction: row; - align-items: center; +const ContentRow = styled.View<{stacked?: boolean}>` + flex-direction: ${({stacked}) => (stacked ? 'column' : 'row')}; + align-items: ${({stacked}) => (stacked ? 'flex-start' : 'center')}; `; const DonutContainer = styled.View` @@ -92,20 +92,26 @@ const DonutContainer = styled.View` margin-right: 14px; `; -const LegendGrid = styled.View` +const LegendGrid = styled.View<{stacked?: boolean}>` flex: 1; flex-direction: row; + flex-wrap: wrap; + row-gap: 10px; justify-content: space-between; + ${({stacked}) => (stacked ? 'align-self: stretch; margin-top: 12px;' : '')} `; -const LegendColumn = styled.View` +// Stacked: one column per row so the labels get the full card width. +const LegendColumn = styled.View<{stacked?: boolean}>` flex: 1; + min-width: ${({stacked}) => (stacked ? '100%' : '120px')}; gap: 10px; `; const LegendItemRow = styled.View` flex-direction: row; align-items: center; + flex-shrink: 1; `; const LegendDot = styled.View<{ @@ -113,6 +119,7 @@ const LegendDot = styled.View<{ }>` width: 9px; height: 9px; + flex-shrink: 0; border-radius: 8px; margin-right: 8px; background-color: ${({color}) => color}; @@ -121,6 +128,7 @@ const LegendDot = styled.View<{ `; const LegendText = styled(BaseText)` + flex-shrink: 1; font-size: 13px; font-style: normal; font-weight: 400; @@ -310,11 +318,12 @@ export const AllocationDonutLegendCard: React.FC<{ }> = ({legendItems, slices, style, header, footer, isLoading}) => { const theme = useTheme(); const {t} = useTranslation(); + const stacked = useIsLargeFont(); const leftColumn = legendItems.slice(0, 3); const rightColumn = legendItems.slice(3); const renderLegendColumn = (items: AllocationLegendItem[]) => { return ( - + {items.map(item => { const dotColor = theme.dark ? item.color.dark : item.color.light; @@ -423,12 +432,12 @@ export const AllocationDonutLegendCard: React.FC<{ return ( {header} - + - + {renderLegendColumn(leftColumn)} {renderLegendColumn(rightColumn)} diff --git a/src/navigation/tabs/home/components/AssetRow.tsx b/src/navigation/tabs/home/components/AssetRow.tsx index 6c77aef80c..1c7605d267 100644 --- a/src/navigation/tabs/home/components/AssetRow.tsx +++ b/src/navigation/tabs/home/components/AssetRow.tsx @@ -24,7 +24,7 @@ import { White, } from '../../../../styles/colors'; import {getDifferenceColor} from '../../../../components/percentage/Percentage'; -import {useAppSelector} from '../../../../utils/hooks'; +import {useAppSelector, useIsLargeFont} from '../../../../utils/hooks'; import {maskIfHidden} from '../../../../utils/hideBalances'; import ChevronRightSvg from './ChevronRightSvg'; import { @@ -43,9 +43,9 @@ const supportedCurrencyOptionLookup = createSupportedCurrencyOptionLookup( const PRESERVED_ASSET_ROW_LOADING_DELAY_MS = 250; const PERCENT_PILL_SKELETON_FILL_VALUE = '-2.22%'; -const Row = styled(TouchableOpacity)<{isLast: boolean}>` - flex-direction: row; - align-items: center; +const Row = styled(TouchableOpacity)<{isLast: boolean; stacked?: boolean}>` + flex-direction: ${({stacked}) => (stacked ? 'column' : 'row')}; + align-items: ${({stacked}) => (stacked ? 'flex-start' : 'center')}; padding: 14px 0; border-bottom-width: ${({isLast}) => (isLast ? 0 : 1)}px; border-bottom-color: ${({theme: {dark}}) => (dark ? LightBlack : LightBlue)}; @@ -57,10 +57,12 @@ const IconContainer = styled.View` align-items: center; justify-content: center; margin-right: 12px; + margin-bottom: 4px; `; -const AssetInfo = styled.View` +const AssetInfo = styled.View<{stacked?: boolean}>` flex: 1; + ${({stacked}) => (stacked ? 'align-self: stretch;' : 'min-width: 90px;')} justify-content: center; `; @@ -79,10 +81,11 @@ const AssetAmount = styled(H7)` color: ${({theme: {dark}}) => (dark ? Slate30 : SlateDark)}; `; -const Values = styled.View` - align-items: flex-end; +const Values = styled.View<{stacked?: boolean}>` + align-items: ${({stacked}) => (stacked ? 'flex-start' : 'flex-end')}; justify-content: center; - margin-right: 12px; + flex-shrink: 1; + ${({stacked}) => (stacked ? 'margin-top: 8px;' : 'margin-right: 12px;')} `; const DeltaFiatSkeletonContainer = styled.View` @@ -107,13 +110,15 @@ const DeltaFiat = styled(BaseText)<{isPositive: boolean; hasPnl: boolean}>` hasPnl ? getDifferenceColor(isPositive, dark) : dark ? Slate30 : SlateDark}; `; -const PercentPill = styled.View` +const PercentPill = styled.View<{stacked?: boolean}>` position: relative; + flex-shrink: 1; border-radius: 50px; padding: 8px 10px; border: 1px solid ${({theme: {dark}}) => (dark ? SlateDark : Slate30)}; background-color: ${({theme: {dark}}) => (dark ? 'transparent' : White)}; - margin-right: 14px; + ${({stacked}) => (stacked ? 'margin-top: 8px;' : 'margin-right: 14px;')} + align-self: flex-start; `; const PercentText = styled(BaseText)<{isPositive: boolean; hasPnl: boolean}>` @@ -139,10 +144,11 @@ const PercentSkeletonOverlay = styled.View` justify-content: center; `; -const ChevronContainer = styled.View<{visible: boolean}>` +const ChevronContainer = styled.View<{visible: boolean; stacked?: boolean}>` width: 9px; align-items: flex-end; opacity: ${({visible}) => (visible ? 1 : 0)}; + ${({stacked}) => (stacked ? 'display: none;' : '')} `; interface Props { @@ -171,6 +177,7 @@ const AssetRow: React.FC = ({ const {t} = useTranslation(); const navigation = useNavigation>(); const theme = useTheme(); + const stacked = useIsLargeFont(); const hideAllBalances = useAppSelector(({APP}) => APP.hideAllBalances); const rowLoading = !!(isPnlLoading || isPopulateLoading); const shouldForceSkeleton = !!forceSkeleton; @@ -281,7 +288,7 @@ const AssetRow: React.FC = ({ if (shouldForceSkeleton) { return ( - + = ({ @@ -362,8 +370,8 @@ const AssetRow: React.FC = ({ /> - - + + {displayItem.name} {hideAllBalances ? ( @@ -388,7 +396,7 @@ const AssetRow: React.FC = ({ {shouldShowRightSide ? ( <> - + {hideAllBalances ? ( <> @@ -430,7 +438,7 @@ const AssetRow: React.FC = ({ )} - + {shouldShowSkeleton ? ( <> @@ -457,7 +465,7 @@ const AssetRow: React.FC = ({ ) : null} - + diff --git a/src/navigation/tabs/home/components/AssetsGainLossDropdown.tsx b/src/navigation/tabs/home/components/AssetsGainLossDropdown.tsx index 52d582eddb..1ab76c2608 100644 --- a/src/navigation/tabs/home/components/AssetsGainLossDropdown.tsx +++ b/src/navigation/tabs/home/components/AssetsGainLossDropdown.tsx @@ -21,17 +21,19 @@ import { import ChevronDown from './ChevronDown'; import type {GainLossMode} from '../../../../utils/portfolio/assets'; -const Container = styled(TouchableOpacity)<{height?: number}>` +const Container = styled(TouchableOpacity)<{$height?: number}>` flex-direction: row; align-items: center; + flex-shrink: 1; border-radius: 50px; padding: 10px 14px; - ${({height}) => (height ? `height: ${height}px;` : '')} + ${({$height}) => ($height ? `min-height: ${$height}px;` : '')} border: 1px solid ${({theme: {dark}}) => (dark ? SlateDark : Slate30)}; background-color: ${({theme: {dark}}) => (dark ? 'transparent' : White)}; `; const Label = styled(BaseText)` + flex-shrink: 1; font-size: 12px; font-style: normal; font-weight: 400; @@ -41,7 +43,8 @@ const Label = styled(BaseText)` `; const Menu = styled.View` - width: 190px; + min-width: 190px; + max-width: 90%; border-radius: 10px; background-color: ${({theme: {dark}}) => (dark ? LightBlack : White)}; shadow-color: ${Black}; @@ -162,7 +165,10 @@ const AssetsGainLossDropdown: React.FC = ({value, onChange, height}) => { return ( <> - + diff --git a/src/navigation/tabs/home/components/AssetsSection.tsx b/src/navigation/tabs/home/components/AssetsSection.tsx index a15f78b5f6..b568b153e9 100644 --- a/src/navigation/tabs/home/components/AssetsSection.tsx +++ b/src/navigation/tabs/home/components/AssetsSection.tsx @@ -43,8 +43,10 @@ const Container = styled.View` const Header = styled.View` flex-direction: row; + flex-wrap: wrap; align-items: center; justify-content: space-between; + gap: 8px; margin: 0 ${ScreenGutter} 0 16px; `; diff --git a/src/navigation/tabs/home/components/FeedbackCard.tsx b/src/navigation/tabs/home/components/FeedbackCard.tsx index 7b9ad9aec4..9ba2ae0036 100644 --- a/src/navigation/tabs/home/components/FeedbackCard.tsx +++ b/src/navigation/tabs/home/components/FeedbackCard.tsx @@ -80,7 +80,7 @@ const EmojiActionContainer = styled.View` const EmojiAction = styled(TouchableOpacity)` width: 44px; - height: 44px; + min-height: 44px; `; const FeedbackCard: React.FC = () => { diff --git a/src/navigation/tabs/home/components/GetVerifiedModal.tsx b/src/navigation/tabs/home/components/GetVerifiedModal.tsx index 8752630ea5..c09b5e17cf 100644 --- a/src/navigation/tabs/home/components/GetVerifiedModal.tsx +++ b/src/navigation/tabs/home/components/GetVerifiedModal.tsx @@ -21,10 +21,29 @@ import IconKycGetVerified from '../../../../../assets/img/kyc_get_verified.svg'; import IconClose from '../../../../../assets/img/close-modal-icon.svg'; // The sheet is fullscreen; this dims the screen and centers the card. -const Backdrop = styled.Pressable` +// The card keeps its natural height and stays centred while it fits; once it no +// longer does, this scrolls instead of the card clipping its own content. +// Centring lives on the content container, not on a `flex-grow` child: a +// stretched child would centre its overflow out of both ends and put the top of +// the card permanently out of reach. +const BackdropScroll = styled.ScrollView.attrs(() => ({ + contentContainerStyle: { + flexGrow: 1, + justifyContent: 'center', + alignItems: 'center', + paddingVertical: 24, + }, + showsVerticalScrollIndicator: false, +}))` flex: 1; - align-items: center; - justify-content: center; +`; + +const Backdrop = styled.Pressable` + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; `; const ModalCard = styled.View` @@ -108,7 +127,8 @@ const GetVerifiedModal: React.FC = () => { backgroundColor={'rgba(0, 0, 0, 0.4)'} onBackdropPress={dismiss} onModalHide={handleModalHide}> - + + {/* Absorb taps on the card. */} {}}> @@ -133,7 +153,7 @@ const GetVerifiedModal: React.FC = () => { - + ); }; diff --git a/src/navigation/tabs/home/components/HomeSection.tsx b/src/navigation/tabs/home/components/HomeSection.tsx index cda8e7598e..79fa8fd233 100644 --- a/src/navigation/tabs/home/components/HomeSection.tsx +++ b/src/navigation/tabs/home/components/HomeSection.tsx @@ -38,6 +38,8 @@ const HomeRowContainer = styled.View` const Header = styled.View` display: flex; flex-direction: row; + flex-wrap: wrap; + gap: 8px; margin: 0 ${ScreenGutter} 0 16px; justify-content: space-between; align-items: center; @@ -45,6 +47,7 @@ const Header = styled.View` const HeaderLeft = styled.View` flex-direction: row; + flex-wrap: wrap; align-items: center; flex-shrink: 1; `; diff --git a/src/navigation/tabs/home/components/LinkingButtons.tsx b/src/navigation/tabs/home/components/LinkingButtons.tsx index 8ac7a7b5b7..6692654f3c 100644 --- a/src/navigation/tabs/home/components/LinkingButtons.tsx +++ b/src/navigation/tabs/home/components/LinkingButtons.tsx @@ -23,6 +23,8 @@ const ButtonsRow = styled.View<{ justify-content: ${({$compactSpacing}) => $compactSpacing ? 'center' : 'space-between'}; flex-direction: row; + flex-wrap: wrap; + row-gap: 12px; align-self: center; width: ${({$maxWidth = MAX_LINKING_BUTTON_ROW_WIDTH}) => Math.min(WIDTH - 24, $maxWidth)}px; @@ -31,12 +33,14 @@ const ButtonsRow = styled.View<{ const ButtonContainer = styled.View<{$compactSpacing?: boolean}>` align-items: center; + flex-shrink: 1; margin: ${({$compactSpacing}) => ($compactSpacing ? '0 30px' : '0')}; `; const ButtonText = styled(BaseText)` font-size: 13px; line-height: 18px; + text-align: center; color: ${({theme: {dark}}) => (dark ? White : Midnight)}; margin-top: 3px; `; diff --git a/src/navigation/tabs/home/components/PortfolioBalance.tsx b/src/navigation/tabs/home/components/PortfolioBalance.tsx index 2ced7d0875..af7496a5aa 100644 --- a/src/navigation/tabs/home/components/PortfolioBalance.tsx +++ b/src/navigation/tabs/home/components/PortfolioBalance.tsx @@ -92,16 +92,21 @@ const PortfolioBalanceHeader = styled(TouchableOpacity)` flex-direction: row; justify-content: space-between; align-items: center; + flex-shrink: 1; + padding-right: 48px; `; const PortfolioBalanceTitle = styled(BaseText)` margin-right: 3px; + flex-shrink: 1; font-size: 13px; line-height: 18px; color: ${({theme: {dark}}) => (dark ? White : SlateDark)}; `; -const PortfolioBalanceText = styled(BaseText)<{$isCompact?: boolean}>` +const PortfolioBalanceText = styled(BaseText).attrs(() => ({ + maxFontSizeMultiplier: 1.5, +}))<{$isCompact?: boolean}>` font-size: ${({$isCompact}) => ($isCompact ? '26px' : '39px')}; font-weight: 700; line-height: ${({$isCompact}) => ($isCompact ? '38px' : '59px')}; @@ -109,7 +114,9 @@ const PortfolioBalanceText = styled(BaseText)<{$isCompact?: boolean}>` margin: 2px 0; `; -const HiddenBalance = styled(H2)` +const HiddenBalance = styled(H2).attrs(() => ({ + maxFontSizeMultiplier: 1.5, +}))` line-height: 50px; margin: 6px 0; `; diff --git a/src/navigation/tabs/home/components/Wallet.tsx b/src/navigation/tabs/home/components/Wallet.tsx index 14e62e71b9..119d042889 100644 --- a/src/navigation/tabs/home/components/Wallet.tsx +++ b/src/navigation/tabs/home/components/Wallet.tsx @@ -35,6 +35,7 @@ import {maskIfHidden} from '../../../../utils/hideBalances'; import {isUnitedKingdomCountry} from '../../../../store/location/location.effects'; import ThresholdBadge from '../../../../components/threshold-badge/ThresholdBadge'; import MultisigBadge from '../../../../components/multisig-badge/MultisigBadge'; +import {useIsLargeFont} from '../../../../utils/hooks'; interface WalletCardComponentProps { wallets: Wallet[]; @@ -132,8 +133,11 @@ const RemainingAssetsContainer = styled.View` padding-bottom: 0px; `; -const ListRow = styled(Row)` - align-items: center; +// Not `styled(Row)`: Row is `flex: 1` (flex-basis 0) and would contribute no +// intrinsic height to this column. +const ListRow = styled.View<{stacked?: boolean}>` + flex-direction: ${({stacked}) => (stacked ? 'column' : 'row')}; + align-items: ${({stacked}) => (stacked ? 'flex-start' : 'center')}; justify-content: space-between; width: 100%; `; @@ -149,9 +153,12 @@ const FooterSupportedNetworkIconContainer = styled( margin-right: 12px; `; -const FooterContainer = styled(Row)` +const FooterContainer = styled.View` + flex-direction: row; + flex-wrap: wrap; align-items: center; justify-content: space-between; + gap: 8px; width: 100%; `; @@ -176,18 +183,18 @@ const ListLeftColumn = styled(Column)` flex: 1; `; -const ListRightColumn = styled(Column)` - align-items: flex-end; +const ListRightColumn = styled(Column)<{stacked?: boolean}>` + align-items: ${({stacked}) => (stacked ? 'flex-start' : 'flex-end')}; justify-content: flex-start; - margin-left: 12px; + ${({stacked}) => (stacked ? 'margin-top: 8px;' : 'margin-left: 12px;')} `; -const ListBalance = styled(BaseText)` +const ListBalance = styled(BaseText)<{stacked?: boolean}>` color: ${({theme: {dark}}) => (dark ? White : Black)}; font-size: 16px; font-weight: 400; line-height: 24px; - text-align: right; + text-align: ${({stacked}) => (stacked ? 'left' : 'right')}; `; const ListPercentageRow = styled.View` @@ -212,6 +219,7 @@ const WalletCardComponent: React.FC = ({ isMultisig, }) => { const {t} = useTranslation(); + const stacked = useIsLargeFont(); const defaultAltCurrency = useAppSelector(({APP}) => APP.defaultAltCurrency); const isUkLocation = useAppSelector(({LOCATION}) => { return isUnitedKingdomCountry(LOCATION.locationData?.countryShortCode); @@ -253,7 +261,7 @@ const WalletCardComponent: React.FC = ({ accessibilityLabel={t('{{key}} wallet', {key: keyName})} onPress={onPress} outlineStyle={context === 'keySelector'}> - + {needsBackup && !pendingTssSession ? ( @@ -277,8 +285,10 @@ const WalletCardComponent: React.FC = ({ )} {keyName} - - {maskIfHidden(hideKeyBalance, amount)} + + + {maskIfHidden(hideKeyBalance, amount)} + {!hideKeyBalance && percentageDifference !== null ? ( ` + flex-direction: ${({stacked}) => (stacked ? 'column' : 'row')}; + align-items: ${({stacked}) => (stacked ? 'flex-start' : 'center')}; margin: 10px 0; `; -const NoteContainer = styled(Column)` - align-items: flex-end; +const NoteContainer = styled(Column)<{stacked?: boolean}>` + align-items: ${({stacked}) => (stacked ? 'flex-start' : 'flex-end')}; + ${({stacked}) => (stacked ? 'align-self: stretch; margin-top: 6px;' : '')} gap: 4px; `; @@ -63,6 +65,7 @@ const ExchangeRateItem = ({ defaultAltCurrencyIsoCode: string; }) => { const {t} = useTranslation(); + const stacked = useIsLargeFont(); const {img, currencyName, currentPrice, average, currencyAbbreviation} = item; const {amount, code} = formatFiatAmountObj( @@ -76,6 +79,7 @@ const ExchangeRateItem = ({ return (
- + {currencyName} {currencyAbbreviation ? ( @@ -95,7 +99,7 @@ const ExchangeRateItem = ({ ) : null} - + {currentPrice ? ( <> diff --git a/src/navigation/tabs/home/screens/AllAssets.tsx b/src/navigation/tabs/home/screens/AllAssets.tsx index be511c9a72..bb2997ef9d 100644 --- a/src/navigation/tabs/home/screens/AllAssets.tsx +++ b/src/navigation/tabs/home/screens/AllAssets.tsx @@ -61,6 +61,7 @@ const ScreenContainer = styled.SafeAreaView` const FiltersRow = styled.View` flex-direction: row; + flex-wrap: wrap; align-items: center; justify-content: space-between; padding: 12px 0 12px; @@ -69,6 +70,7 @@ const FiltersRow = styled.View` const SearchInputContainer = styled.View` flex: 1; + min-width: 160px; border-radius: 50px; flex-direction: row; align-items: center; diff --git a/src/navigation/tabs/settings/about/screens/SendFeedback.tsx b/src/navigation/tabs/settings/about/screens/SendFeedback.tsx index 3dc3cb4462..db73c5a767 100644 --- a/src/navigation/tabs/settings/about/screens/SendFeedback.tsx +++ b/src/navigation/tabs/settings/about/screens/SendFeedback.tsx @@ -84,7 +84,7 @@ const EmojiActionContainer = styled.View` const EmojiAction = styled(TouchableOpacity)` width: 44px; - height: 44px; + min-height: 44px; `; const ListItem = styled(Setting)` diff --git a/src/navigation/tabs/settings/external-services/styled/ExternalServicesDetails.tsx b/src/navigation/tabs/settings/external-services/styled/ExternalServicesDetails.tsx index bfb4d0fd47..d74b2a9b18 100644 --- a/src/navigation/tabs/settings/external-services/styled/ExternalServicesDetails.tsx +++ b/src/navigation/tabs/settings/external-services/styled/ExternalServicesDetails.tsx @@ -96,7 +96,7 @@ export const RemoveCta = styled(TouchableOpacity)` flex-direction: row; justify-content: center; align-items: center; - height: 60px; + min-height: 60px; margin-top: 30px; `; diff --git a/src/navigation/tabs/settings/external-services/styled/ExternalServicesSettings.tsx b/src/navigation/tabs/settings/external-services/styled/ExternalServicesSettings.tsx index b726a5e90f..5cafba9c4d 100644 --- a/src/navigation/tabs/settings/external-services/styled/ExternalServicesSettings.tsx +++ b/src/navigation/tabs/settings/external-services/styled/ExternalServicesSettings.tsx @@ -17,7 +17,7 @@ export const PrRow = styled(TouchableOpacity)` flex-direction: row; justify-content: space-between; align-items: center; - height: 60px; + min-height: 60px; `; export const PrRowLeft = styled.View` diff --git a/src/navigation/tabs/settings/general/screens/Theme.tsx b/src/navigation/tabs/settings/general/screens/Theme.tsx index 75c90482a3..5e5c45a75b 100644 --- a/src/navigation/tabs/settings/general/screens/Theme.tsx +++ b/src/navigation/tabs/settings/general/screens/Theme.tsx @@ -1,6 +1,11 @@ import React, {useLayoutEffect, useRef, useState} from 'react'; import {useTranslation} from 'react-i18next'; -import {ColorSchemeName, Pressable, View, SafeAreaView} from 'react-native'; +import { + ColorSchemeName, + Pressable, + ScrollView, + SafeAreaView, +} from 'react-native'; import {TEST_MODE_NETWORK} from '@env'; import Checkbox from '../../../../../components/checkbox/Checkbox'; import { @@ -105,7 +110,7 @@ const ThemeSettings: React.FC = ({navigation}) => { return ( - + onSetThemePress('light')}> {t('Light Mode')} = ({navigation}) => { checked={!currentTheme || currentTheme === 'unspecified'} /> - + ); }; diff --git a/src/navigation/tabs/shop/bill/components/BillItem.tsx b/src/navigation/tabs/shop/bill/components/BillItem.tsx index 32b58a9079..6958aeee1f 100644 --- a/src/navigation/tabs/shop/bill/components/BillItem.tsx +++ b/src/navigation/tabs/shop/bill/components/BillItem.tsx @@ -86,7 +86,7 @@ const AccountActions = styled.View` `; const PayButton = styled.View` - height: 32px; + min-height: 32px; background-color: ${Action}; border-radius: 50px; max-width: 90px; diff --git a/src/navigation/tabs/shop/components/FilterSheet.tsx b/src/navigation/tabs/shop/components/FilterSheet.tsx index af161c555b..694cb3949c 100644 --- a/src/navigation/tabs/shop/components/FilterSheet.tsx +++ b/src/navigation/tabs/shop/components/FilterSheet.tsx @@ -45,7 +45,7 @@ interface PillParams { } const Pill = styled.View` - height: 40px; + min-height: 40px; align-items: center; justify-content: center; border: ${({theme: {dark}}) => `2px solid ${dark ? LinkBlue : Action}`}; diff --git a/src/navigation/tabs/shop/components/GiftCardDenomSelector.tsx b/src/navigation/tabs/shop/components/GiftCardDenomSelector.tsx index 2157ebd5cd..3c315e631b 100644 --- a/src/navigation/tabs/shop/components/GiftCardDenomSelector.tsx +++ b/src/navigation/tabs/shop/components/GiftCardDenomSelector.tsx @@ -17,7 +17,7 @@ const Selector = styled.View` `; const ChangeDenomButton = styled.View` - height: 39px; + min-height: 39px; width: 39px; border: 1px solid black; display: flex; diff --git a/src/navigation/tabs/shop/components/GiftCardItem.tsx b/src/navigation/tabs/shop/components/GiftCardItem.tsx index f72702f5ff..3b702843ed 100644 --- a/src/navigation/tabs/shop/components/GiftCardItem.tsx +++ b/src/navigation/tabs/shop/components/GiftCardItem.tsx @@ -11,7 +11,7 @@ const GiftCardItemContainer = styled.View` display: flex; flex-direction: row; align-items: center; - height: 55px; + min-height: 55px; margin: 16px 0 16px 20px; `; diff --git a/src/navigation/tabs/shop/components/MerchantItem.tsx b/src/navigation/tabs/shop/components/MerchantItem.tsx index 30391e07de..b4b71d27a8 100644 --- a/src/navigation/tabs/shop/components/MerchantItem.tsx +++ b/src/navigation/tabs/shop/components/MerchantItem.tsx @@ -55,7 +55,7 @@ const MerchantDescription = styled(Paragraph)` const PromoFooter = styled.View` background-color: ${({theme}) => theme.colors.background}; - height: 48px; + min-height: 48px; padding-left: 10px; padding-right: 20px; flex-direction: row; diff --git a/src/navigation/tabs/shop/components/MyGiftCards.tsx b/src/navigation/tabs/shop/components/MyGiftCards.tsx index b8afc53989..c55b52037e 100644 --- a/src/navigation/tabs/shop/components/MyGiftCards.tsx +++ b/src/navigation/tabs/shop/components/MyGiftCards.tsx @@ -34,7 +34,7 @@ const NoGiftCards = styled.View` border-radius: 30px; align-items: center; justify-content: center; - height: 50px; + min-height: 50px; margin: 10px ${horizontalPadding}px; `; diff --git a/src/navigation/tabs/shop/components/PhoneCountryModal.tsx b/src/navigation/tabs/shop/components/PhoneCountryModal.tsx index ed97eddd36..eb8916fb37 100644 --- a/src/navigation/tabs/shop/components/PhoneCountryModal.tsx +++ b/src/navigation/tabs/shop/components/PhoneCountryModal.tsx @@ -19,7 +19,7 @@ import {ActiveOpacity} from '../../../../components/styled/Containers'; const ModalHeader = styled.View` flex-direction: row; padding: 20px; - height: 55px; + min-height: 55px; align-items: center; padding-bottom: 0; padding-top: 10px; diff --git a/src/navigation/tabs/shop/components/styled/ShopTabComponents.tsx b/src/navigation/tabs/shop/components/styled/ShopTabComponents.tsx index e9b90eb67b..71d8805b66 100644 --- a/src/navigation/tabs/shop/components/styled/ShopTabComponents.tsx +++ b/src/navigation/tabs/shop/components/styled/ShopTabComponents.tsx @@ -56,13 +56,16 @@ export const SectionSpacer = styled.View<{height?: number}>` export const SectionHeaderContainer = styled.View` display: flex; flex-direction: row; + flex-wrap: wrap; align-items: center; + gap: 8px; `; export const SectionHeaderButton = styled(Link)` margin-top: 38px; margin-bottom: 12px; font-weight: 500; + flex-shrink: 1; `; export const SectionHeader = styled(BaseText)` @@ -72,6 +75,7 @@ export const SectionHeader = styled(BaseText)` margin-bottom: 16px; margin-top: 40px; flex-grow: 1; + flex-shrink: 1; font-weight: 500; `; diff --git a/src/navigation/tabs/shop/gift-card/screens/EnterPhone.tsx b/src/navigation/tabs/shop/gift-card/screens/EnterPhone.tsx index 2ca54f8ce9..0b7192b9f0 100644 --- a/src/navigation/tabs/shop/gift-card/screens/EnterPhone.tsx +++ b/src/navigation/tabs/shop/gift-card/screens/EnterPhone.tsx @@ -42,7 +42,7 @@ const AreaCodeContainer = styled.View` justify-content: center; padding-left: 15px; padding-right: 10px; - height: 37px; + min-height: 37px; `; const AreaCode = styled(BaseText)` diff --git a/src/navigation/wallet-connect/screens/WalletConnectRequestDetails.tsx b/src/navigation/wallet-connect/screens/WalletConnectRequestDetails.tsx index 5ea28423c7..f5abd062e1 100644 --- a/src/navigation/wallet-connect/screens/WalletConnectRequestDetails.tsx +++ b/src/navigation/wallet-connect/screens/WalletConnectRequestDetails.tsx @@ -69,7 +69,7 @@ const AddressContainer = styled.View` const AddressTextContainer = styled(TouchableOpacity)` background-color: ${({theme}) => (theme.dark ? LightBlack : NeutralSlate)}; border-radius: 40px; - height: 37px; + min-height: 37px; width: 150px; margin-left: 2px; justify-content: center; diff --git a/src/navigation/wallet/components/AddressCard.tsx b/src/navigation/wallet/components/AddressCard.tsx index 6611b99241..26729e077c 100644 --- a/src/navigation/wallet/components/AddressCard.tsx +++ b/src/navigation/wallet/components/AddressCard.tsx @@ -24,7 +24,7 @@ const ListCard = styled(TouchableOpacity)` align-items: center; justify-content: space-between; padding: 15px; - height: 75px; + min-height: 75px; `; const RecipientAmount = styled(BaseText)` @@ -45,7 +45,7 @@ const ContactImageContainer = styled.View` const AddressCard: React.FC = ({recipient}) => { return ( - + diff --git a/src/navigation/wallet/components/OptionsSheet.tsx b/src/navigation/wallet/components/OptionsSheet.tsx index 03995439eb..62c10e9edb 100644 --- a/src/navigation/wallet/components/OptionsSheet.tsx +++ b/src/navigation/wallet/components/OptionsSheet.tsx @@ -29,6 +29,10 @@ import ClockOutlineIcon from '../../../../assets/img/icon-clock-outline.svg'; import AlertTriangleIcon from '../../../../assets/img/icon-alert-triangle.svg'; import InfoIcon from '../../../components/icons/info/Info'; +const OptionsScroll = styled.ScrollView` + flex-grow: 0; +`; + const OptionsHeaderContainer = styled.View<{cardStyle?: boolean}>` flex-direction: row; align-items: center; @@ -227,103 +231,109 @@ const OptionsSheet = ({ ) : null} - {options.map( - ( - { - img, - imgSrc, - title: optionTitle, - description, - badge, - subDescriptionItems, - cardStyle, - onPress, - optionElement, - showChevron, + + {options.map( + ( + { + img, + imgSrc, + title: optionTitle, + description, + badge, + subDescriptionItems, + cardStyle, + onPress, + optionElement, + showChevron, + }, + index, + ) => { + return ( + { + closeModal(); + await sleep(500); + onPress(); + }}> + {optionElement ? ( + <>{optionElement()} + ) : ( + <> + {img && {img}} + {imgSrc && ( + + + + )} + + {badge ? ( + + {badge} + + ) : null} + {optionTitle ? ( + + {optionTitle} + + ) : null} + + {description} + + {subDescriptionItems && + subDescriptionItems.length > 0 && ( + + {subDescriptionItems.map((item, itemIndex) => { + const ItemIcon = subDescriptionIcons[item.icon]; + return ( + + {itemIndex > 0 && ( + + )} + + + + + + {item.text} + + + + ); + })} + + )} + + {showChevron && ( + + + + )} + + )} + + ); }, - index, - ) => { - return ( - { - closeModal(); - await sleep(500); - onPress(); - }}> - {optionElement ? ( - <>{optionElement()} - ) : ( - <> - {img && {img}} - {imgSrc && ( - - - - )} - - {badge ? ( - - {badge} - - ) : null} - {optionTitle ? ( - - {optionTitle} - - ) : null} - - {description} - - {subDescriptionItems && - subDescriptionItems.length > 0 && ( - - {subDescriptionItems.map((item, itemIndex) => { - const ItemIcon = subDescriptionIcons[item.icon]; - return ( - - {itemIndex > 0 && } - - - - - - {item.text} - - - - ); - })} - - )} - - {showChevron && ( - - - - )} - - )} - - ); - }, - )} + )} + ); diff --git a/src/navigation/wallet/components/RangeDateSelector.tsx b/src/navigation/wallet/components/RangeDateSelector.tsx index 408ab163f5..6cb27005d4 100644 --- a/src/navigation/wallet/components/RangeDateSelector.tsx +++ b/src/navigation/wallet/components/RangeDateSelector.tsx @@ -46,8 +46,8 @@ const ButtonText = styled(BaseText)<{isActive: string; label: string}>` `; const LinkButton = styled(TouchableOpacity)<{isActive: string; label: string}>` - height: 40px; - width: 50px; + min-height: 40px; + min-width: 50px; border-radius: 18px; align-items: center; justify-content: center; diff --git a/src/navigation/wallet/components/ReceiveAddress.tsx b/src/navigation/wallet/components/ReceiveAddress.tsx index dd4a2a0a9b..9b057e8384 100644 --- a/src/navigation/wallet/components/ReceiveAddress.tsx +++ b/src/navigation/wallet/components/ReceiveAddress.tsx @@ -66,7 +66,7 @@ const CopyToClipboard = styled(TouchableOpacity)` border: 1px solid #9ba3ae; border-radius: 4px; padding: 0 10px; - height: 55px; + min-height: 55px; align-items: center; flex-direction: row; `; diff --git a/src/navigation/wallet/components/ReceiveAddressHeader.tsx b/src/navigation/wallet/components/ReceiveAddressHeader.tsx index 3bd263e4ff..8874e0efd2 100644 --- a/src/navigation/wallet/components/ReceiveAddressHeader.tsx +++ b/src/navigation/wallet/components/ReceiveAddressHeader.tsx @@ -42,7 +42,7 @@ const BchHeaderAction = styled(TouchableOpacity)<{isActive: boolean}>` margin: 0 10px -1px; border-bottom-color: ${({isActive}) => (isActive ? Action : 'transparent')}; border-bottom-width: 1px; - height: 60px; + min-height: 60px; `; const BchHeaderActionText = styled(BaseText)<{isActive: boolean}>` diff --git a/src/navigation/wallet/components/RecoveryPhrase.tsx b/src/navigation/wallet/components/RecoveryPhrase.tsx index c0621ff48a..b9d699a12d 100644 --- a/src/navigation/wallet/components/RecoveryPhrase.tsx +++ b/src/navigation/wallet/components/RecoveryPhrase.tsx @@ -158,7 +158,7 @@ const CurrencySelectorContainer = styled.View` const CurrencyContainer = styled(TouchableOpacity)` background: ${({theme}) => (theme.dark ? LightBlack : NeutralSlate)}; padding: 0 20px; - height: 55px; + min-height: 55px; border: 1px solid ${({theme}) => (theme.dark ? LightBlack : NeutralSlate)}; border-top-left-radius: 4px; border-top-right-radius: 4px; diff --git a/src/navigation/wallet/components/SendToContact.tsx b/src/navigation/wallet/components/SendToContact.tsx index ff36f8f276..1c95a0e737 100644 --- a/src/navigation/wallet/components/SendToContact.tsx +++ b/src/navigation/wallet/components/SendToContact.tsx @@ -86,7 +86,7 @@ const SendToContact = () => { return ( <> - + ` align-items: center; justify-content: center; padding: 0 11px; - height: ${({height}) => (height ? height : '100%')}; + min-height: ${({height}) => (height ? height : '37px')}; max-width: 202px; `; diff --git a/src/navigation/wallet/components/TSSOnboardingModal.tsx b/src/navigation/wallet/components/TSSOnboardingModal.tsx index f4a32a68c3..be6daf3613 100644 --- a/src/navigation/wallet/components/TSSOnboardingModal.tsx +++ b/src/navigation/wallet/components/TSSOnboardingModal.tsx @@ -205,7 +205,7 @@ const PaginationText = styled(BaseText)` `; const NextButton = styled(TouchableOpacity)` - height: 50px; + min-height: 50px; min-width: 100px; padding: 0 16px; border-radius: 8px; diff --git a/src/navigation/wallet/screens/AccountDetails.tsx b/src/navigation/wallet/screens/AccountDetails.tsx index f9308591f5..e55ab69100 100644 --- a/src/navigation/wallet/screens/AccountDetails.tsx +++ b/src/navigation/wallet/screens/AccountDetails.tsx @@ -121,9 +121,7 @@ import {RootStacks} from '../../../Root'; import {TabsScreens} from '../../tabs/TabsStack'; import {CoinbaseScreens} from '../../coinbase/CoinbaseGroup'; import DropdownOption from '../components/DropdownOption'; -import TransactionRow, { - TRANSACTION_ROW_HEIGHT, -} from '../../../components/list/TransactionRow'; +import TransactionRow from '../../../components/list/TransactionRow'; import ContactIcon from '../../tabs/contacts/components/ContactIcon'; import { TRANSACTION_ICON_SIZE, @@ -235,12 +233,6 @@ interface AccountProposalsProps { [key: string]: TransactionProposal[]; } -const transactionItemLayout = (_data: any, index: number) => ({ - length: TRANSACTION_ROW_HEIGHT, - offset: TRANSACTION_ROW_HEIGHT * index, - index, -}); - const BorderBottom = styled.View` border-bottom-width: 1px; border-bottom-color: ${({theme: {dark}}) => (dark ? LightBlack : Air)}; @@ -268,7 +260,7 @@ const WalletListHeaderLabel = styled.View<{ const CopyToClipboardContainer = styled.View` justify-content: center; - height: 20px; + min-height: 20px; `; const HeaderContainer = styled.View` @@ -278,7 +270,7 @@ const HeaderContainer = styled.View` const TransactionSectionHeaderContainer = styled.View` padding: ${ScreenGutter}; background-color: ${({theme: {dark}}) => (dark ? LightBlack : '#F5F6F7')}; - height: 55px; + min-height: 55px; width: 100%; display: flex; flex-direction: row; @@ -295,7 +287,7 @@ const LockedBalanceContainer = styled(TouchableOpacity)` padding: ${ScreenGutter}; justify-content: center; align-items: center; - height: 75px; + min-height: 75px; `; const Description = styled(BaseText)` @@ -385,7 +377,7 @@ const AccountAddressBadge = ({address}: AccountAddressBadgeProps) => { + style={{alignSelf: 'center', width: 'auto', minHeight: 25}}> {formatCryptoAddress(address)} {!copied ? : } @@ -1849,7 +1841,6 @@ const AccountDetails: React.FC = ({route}) => { maxToRenderPerBatch: 15, })} ListEmptyComponent={listEmptyComponentForTab} - getItemLayout={transactionItemLayout} /> (theme.dark ? LuckySevens : Slate)}; border-top-left-radius: 4px; border-top-right-radius: 4px; @@ -127,7 +127,7 @@ const AssociatedWallet = styled(TouchableOpacity)` const AssociatedAccountNoTouchable = styled.View` padding: 0 10px; - height: 64px; + min-height: 64px; border: 0.75px solid ${({theme}) => (theme.dark ? LuckySevens : Slate)}; border-top-left-radius: 4px; border-top-right-radius: 4px; diff --git a/src/navigation/wallet/screens/AddingOptions.tsx b/src/navigation/wallet/screens/AddingOptions.tsx index f406b34b02..e6aa119761 100644 --- a/src/navigation/wallet/screens/AddingOptions.tsx +++ b/src/navigation/wallet/screens/AddingOptions.tsx @@ -326,7 +326,7 @@ const AddingOptions: React.FC = () => { {optionList.map(({cta, id, title, description}: Option) => ( { haptic('impactLight'); diff --git a/src/navigation/wallet/screens/Backup.tsx b/src/navigation/wallet/screens/Backup.tsx index 622c7875a8..ee57cc7fc3 100644 --- a/src/navigation/wallet/screens/Backup.tsx +++ b/src/navigation/wallet/screens/Backup.tsx @@ -58,7 +58,12 @@ export type BackupParamList = { const BackupContainer = styled.SafeAreaView` flex: 1; - align-items: center; +`; + +const BackupScroll = styled.ScrollView.attrs(() => ({ + contentContainerStyle: {alignItems: 'center', flexGrow: 1}, +}))` + flex: 1; `; type BackupNavigation = { @@ -177,30 +182,32 @@ const BackupScreen = ({route}: BackupScreenProps) => { return ( - {BackupImage[themeType]} - - -

{t('Would you like to backup your key?')}

-
-
- - - - {t( - "If you delete the BitPay app or lose your device, you'll need your recovery phrase regain access to your funds.", - )} - - - - - - + + {BackupImage[themeType]} + + +

{t('Would you like to backup your key?')}

+
+
+ + + + {t( + "If you delete the BitPay app or lose your device, you'll need your recovery phrase regain access to your funds.", + )} + + + + + + +
); }; diff --git a/src/navigation/wallet/screens/CreateMultisig.tsx b/src/navigation/wallet/screens/CreateMultisig.tsx index 2a12da47b8..332e975a32 100644 --- a/src/navigation/wallet/screens/CreateMultisig.tsx +++ b/src/navigation/wallet/screens/CreateMultisig.tsx @@ -138,8 +138,8 @@ const RoundButton = styled.View` display: flex; align-items: center; justify-content: center; - width: 42px; - height: 42px; + min-width: 42px; + min-height: 42px; margin: 10px; border-radius: 30px; border: 1px solid ${({theme: {dark}}) => (dark ? White : Action)}; diff --git a/src/navigation/wallet/screens/GlobalSelect.tsx b/src/navigation/wallet/screens/GlobalSelect.tsx index 17149a9162..fed6b4cacd 100644 --- a/src/navigation/wallet/screens/GlobalSelect.tsx +++ b/src/navigation/wallet/screens/GlobalSelect.tsx @@ -115,7 +115,7 @@ import {useOngoingProcess, useTokenContext} from '../../../contexts'; import {logManager} from '../../../managers/LogManager'; const ModalHeader = styled.View` - height: 50px; + min-height: 50px; margin-right: 10px; margin-left: 10px; display: flex; diff --git a/src/navigation/wallet/screens/InviteCosigners.tsx b/src/navigation/wallet/screens/InviteCosigners.tsx index 717d5c7be6..e6bcb628d2 100644 --- a/src/navigation/wallet/screens/InviteCosigners.tsx +++ b/src/navigation/wallet/screens/InviteCosigners.tsx @@ -274,8 +274,8 @@ const HelpStepRow = styled.View` `; const HelpStepBubble = styled.View` - width: 18px; - height: 18px; + min-width: 18px; + min-height: 18px; border-radius: 9px; align-items: center; justify-content: center; @@ -824,7 +824,7 @@ const InviteCosigners: React.FC = ({route}) => { - + {isCeremonyComplete ? ( <> diff --git a/src/navigation/wallet/screens/PaperWallet.tsx b/src/navigation/wallet/screens/PaperWallet.tsx index b870809474..6759838ef7 100644 --- a/src/navigation/wallet/screens/PaperWallet.tsx +++ b/src/navigation/wallet/screens/PaperWallet.tsx @@ -94,7 +94,7 @@ const SelectedOptionContainer = styled.View` flex-direction: row; justify-content: space-between; align-items: center; - height: 40px; + min-height: 40px; padding: 0px 14px; background: ${({theme: {dark}}) => (dark ? LightBlack : NeutralSlate)}; border-radius: 12px; diff --git a/src/navigation/wallet/screens/SelectInputs.tsx b/src/navigation/wallet/screens/SelectInputs.tsx index 3e60f29917..95a6416bae 100644 --- a/src/navigation/wallet/screens/SelectInputs.tsx +++ b/src/navigation/wallet/screens/SelectInputs.tsx @@ -78,7 +78,7 @@ const ItemRowContainer = styled.View` align-items: center; flex-direction: row; justify-content: space-between; - height: 55px; + min-height: 55px; `; const SelectInputsContainer = styled.SafeAreaView` @@ -107,7 +107,7 @@ const DropdownRow = styled(TouchableOpacity)` flex-direction: row; justify-content: space-between; flex-wrap: nowrap; - height: 48px; + min-height: 48px; `; const DropdownTitle = styled.View` diff --git a/src/navigation/wallet/screens/SendToOptions.tsx b/src/navigation/wallet/screens/SendToOptions.tsx index 1b36e2aab6..b698dcc96c 100644 --- a/src/navigation/wallet/screens/SendToOptions.tsx +++ b/src/navigation/wallet/screens/SendToOptions.tsx @@ -46,7 +46,7 @@ export const RecipientRowContainer = styled.View` align-items: center; flex-direction: row; justify-content: space-between; - height: 55px; + min-height: 55px; `; export const RecipientContainer = styled.View` diff --git a/src/navigation/wallet/screens/TransactionProposalNotifications.tsx b/src/navigation/wallet/screens/TransactionProposalNotifications.tsx index fd40686af5..8bef219aaa 100644 --- a/src/navigation/wallet/screens/TransactionProposalNotifications.tsx +++ b/src/navigation/wallet/screens/TransactionProposalNotifications.tsx @@ -40,7 +40,6 @@ import {RefreshControl, SectionList, View} from 'react-native'; import TransactionProposalRow from '../../../components/list/TransactionProposalRow'; import {Air, LightBlack, SlateDark, White} from '../../../styles/colors'; import {formatCurrencyAbbreviation, sleep} from '../../../utils/helper-methods'; -import {TRANSACTION_ROW_HEIGHT} from '../../../components/list/TransactionRow'; import {findWalletById} from '../../../store/wallet/utils/wallet'; import {useTranslation} from 'react-i18next'; import {CurrencyImage} from '../../../components/currency-image/CurrencyImage'; @@ -84,7 +83,7 @@ const ListHeaderPadding = styled.View` const TransactionSectionHeaderContainer = styled.View` padding: ${ScreenGutter}; background-color: ${({theme: {dark}}) => (dark ? LightBlack : '#F5F6F7')}; - height: 55px; + min-height: 55px; width: 100%; display: flex; flex-direction: row; @@ -541,15 +540,6 @@ const TransactionProposalNotifications = () => { [], ); - const getItemLayout = useCallback( - (data: any, index: number) => ({ - length: TRANSACTION_ROW_HEIGHT, - offset: TRANSACTION_ROW_HEIGHT * index, - index, - }), - [], - ); - const listFooterComponent = () => { return ( <> @@ -676,7 +666,6 @@ const TransactionProposalNotifications = () => { ListFooterComponent={listFooterComponent} onEndReachedThreshold={0.5} maxToRenderPerBatch={15} - getItemLayout={getItemLayout} /> {txpsToSign && Object.values(txpsToSign)[0] ? ( diff --git a/src/navigation/wallet/screens/WalletDetails.tsx b/src/navigation/wallet/screens/WalletDetails.tsx index 1910aa03e2..1deca00890 100644 --- a/src/navigation/wallet/screens/WalletDetails.tsx +++ b/src/navigation/wallet/screens/WalletDetails.tsx @@ -207,7 +207,7 @@ const BalanceContainer = styled.View` const TransactionSectionHeaderContainer = styled.View` padding: ${ScreenGutter}; background-color: ${({theme: {dark}}) => (dark ? LightBlack : '#F5F6F7')}; - height: 55px; + min-height: 55px; width: 100%; display: flex; flex-direction: row; @@ -235,7 +235,7 @@ const LockedBalanceContainer = styled(TouchableOpacity)` padding: ${ScreenGutter}; justify-content: center; align-items: center; - height: 75px; + min-height: 75px; `; const Description = styled(BaseText)` diff --git a/src/navigation/wallet/screens/exchange-rate/ExchangeRateScreenLayout.tsx b/src/navigation/wallet/screens/exchange-rate/ExchangeRateScreenLayout.tsx index f3454cb530..43aa002715 100644 --- a/src/navigation/wallet/screens/exchange-rate/ExchangeRateScreenLayout.tsx +++ b/src/navigation/wallet/screens/exchange-rate/ExchangeRateScreenLayout.tsx @@ -85,7 +85,7 @@ const WalletCard = styled(TouchableOpacity)` align-items: center; justify-content: space-between; padding: 15px; - height: 75px; + min-height: 75px; `; const WalletLeft = styled.View` diff --git a/src/navigation/wallet/screens/exchange-rate/UkExchangeRateDisclosures.tsx b/src/navigation/wallet/screens/exchange-rate/UkExchangeRateDisclosures.tsx index 42749c96fe..d8f3e6aed0 100644 --- a/src/navigation/wallet/screens/exchange-rate/UkExchangeRateDisclosures.tsx +++ b/src/navigation/wallet/screens/exchange-rate/UkExchangeRateDisclosures.tsx @@ -23,7 +23,7 @@ const DisclosureRow = styled.View` `; const DisclosureBullet = styled(BaseText)` - width: 16px; + min-width: 16px; font-size: 12px; font-style: normal; font-weight: 400; diff --git a/src/navigation/wallet/screens/send/TransactionLevel.tsx b/src/navigation/wallet/screens/send/TransactionLevel.tsx index e2bc3d58b9..5e0af17158 100644 --- a/src/navigation/wallet/screens/send/TransactionLevel.tsx +++ b/src/navigation/wallet/screens/send/TransactionLevel.tsx @@ -111,7 +111,7 @@ export const FeeLevelStep = styled.View<{isLast?: boolean}>` flex-direction: row; align-items: center; display: flex; - height: 40px; + min-height: 40px; `; export const FeeLevelStepCircle = styled.Pressable<{ diff --git a/src/navigation/wallet/screens/send/confirm/Confirm.tsx b/src/navigation/wallet/screens/send/confirm/Confirm.tsx index a07859fb0c..3dfb80db48 100644 --- a/src/navigation/wallet/screens/send/confirm/Confirm.tsx +++ b/src/navigation/wallet/screens/send/confirm/Confirm.tsx @@ -146,7 +146,7 @@ export const Setting = styled(TouchableOpacity)` align-items: center; flex-direction: row; flex-wrap: nowrap; - height: 58px; + min-height: 58px; `; export const SettingTitle = styled(BaseText)` diff --git a/src/navigation/wallet/screens/send/confirm/Shared.tsx b/src/navigation/wallet/screens/send/confirm/Shared.tsx index 257d7b15a6..73e6b325e1 100644 --- a/src/navigation/wallet/screens/send/confirm/Shared.tsx +++ b/src/navigation/wallet/screens/send/confirm/Shared.tsx @@ -88,7 +88,7 @@ export const DetailContainer = styled.View` minHeight ? `min-height: ${minHeight}px;` : 'min-height: 60px'} ${({minHeight}) => (minHeight ? 'padding: 0;' : 'padding: 20px 0;')} justify-content: center; - ${({height}) => (height ? `height: ${height}px;` : '')} + ${({height}) => (height ? `min-height: ${height}px;` : '')} `; export const PressableDetailContainer = styled( @@ -97,12 +97,13 @@ export const PressableDetailContainer = styled( min-height: 60px; padding: 20px 0; justify-content: center; - ${({height}) => (height ? `height: ${height}px;` : '')} + ${({height}) => (height ? `min-height: ${height}px;` : '')} `; export const DetailRow = styled(Row)` align-items: center; justify-content: space-between; + gap: 8px; `; export const DetailColumn = styled(Column)` @@ -122,7 +123,7 @@ export const ConfirmSubText = styled(H7)` `; export const SendToPillContainer = styled.View` - height: 37px; + min-height: 37px; `; // Row UI diff --git a/src/navigation/wallet/screens/wallet-settings/WalletInformation.tsx b/src/navigation/wallet/screens/wallet-settings/WalletInformation.tsx index ca437836ba..de6a4d9da0 100644 --- a/src/navigation/wallet/screens/wallet-settings/WalletInformation.tsx +++ b/src/navigation/wallet/screens/wallet-settings/WalletInformation.tsx @@ -49,7 +49,7 @@ const InfoSettingsRow = styled.View` align-items: center; flex-direction: row; flex-wrap: nowrap; - height: 58px; + min-height: 58px; `; const SettingsHeader = styled(InfoSettingsRow)` diff --git a/src/navigation/zenledger/screens/ZenLedgerIntro.tsx b/src/navigation/zenledger/screens/ZenLedgerIntro.tsx index e63a721b71..2e553aef91 100644 --- a/src/navigation/zenledger/screens/ZenLedgerIntro.tsx +++ b/src/navigation/zenledger/screens/ZenLedgerIntro.tsx @@ -30,7 +30,9 @@ const ZenledgerContainer = styled.SafeAreaView` justify-content: center; `; -const ZenLedgerIntroContainer = styled.View` +const ZenLedgerIntroContainer = styled.ScrollView.attrs(() => ({ + contentContainerStyle: {paddingBottom: 220}, +}))` margin-top: 40px; border-radius: 10px; padding: ${ScreenGutter}; diff --git a/src/styles/tabNavigator.ts b/src/styles/tabNavigator.ts index bc25d114b6..bb9b82c332 100644 --- a/src/styles/tabNavigator.ts +++ b/src/styles/tabNavigator.ts @@ -6,13 +6,21 @@ import { SlateDark, White, } from './colors'; -import {Platform} from 'react-native'; +import {Platform, useWindowDimensions} from 'react-native'; import {MaterialTopTabNavigationOptions} from '@react-navigation/material-top-tabs'; import {useTheme} from 'styled-components/native'; import {useAppSelector} from '../utils/hooks'; const gutter = 5; +/** + * This bar's width, height and indicator are all fixed pixel values, so the + * label can only grow as far as the pill can. `tabBarAllowFontScaling` is a + * boolean (no max multiplier), so the cap is applied by scaling the geometry + * and the label size together and turning OS scaling off. + */ +const MAX_TAB_FONT_SCALE = 1.4; + export const ScreenOptions = ( {fontSize, numTabs, marginHorizontal, tabWidth, langAdjustments} = { fontSize: 16, @@ -22,9 +30,16 @@ export const ScreenOptions = ( langAdjustments: false, }, ): MaterialTopTabNavigationOptions => { - const totalWidth = tabWidth * numTabs + gutter * 4; const {dark} = useTheme(); const defaultLanguage = useAppSelector(({APP}) => APP.defaultLanguage); + const {fontScale, width: screenWidth} = useWindowDimensions(); + const baseWidth = tabWidth * numTabs + gutter * 4; + const scale = Math.min( + fontScale, + MAX_TAB_FONT_SCALE, + Math.max(1, (screenWidth - 24) / baseWidth), + ); + const totalWidth = baseWidth * scale; const getLangAdjustments = ( lang: string, @@ -65,21 +80,23 @@ export const ScreenOptions = ( return { swipeEnabled: false, + tabBarAllowFontScaling: false, tabBarIndicatorStyle: { - height: langAdjustments - ? getLangAdjustments(defaultLanguage).tabBarIndicatorHeight - : 46, + height: + (langAdjustments + ? getLangAdjustments(defaultLanguage).tabBarIndicatorHeight + : 46) * scale, borderRadius: 50, backgroundColor: Action, - width: tabWidth, + width: tabWidth * scale, margin: gutter, - marginHorizontal, + marginHorizontal: marginHorizontal * scale, }, tabBarActiveTintColor: White, tabBarInactiveTintColor: dark ? White : SlateDark, tabBarPressColor: dark ? Black : NeutralSlate, tabBarLabelStyle: { - fontSize, + fontSize: fontSize * scale, textTransform: 'none', fontWeight: '500', paddingVertical: Platform.select({ @@ -97,9 +114,10 @@ export const ScreenOptions = ( borderRadius: 50, backgroundColor: dark ? LightBlack : NeutralSlate, elevation: 0, - height: langAdjustments - ? getLangAdjustments(defaultLanguage).tabBarHeight - : 56, + height: + (langAdjustments + ? getLangAdjustments(defaultLanguage).tabBarHeight + : 56) * scale, }, }; }; diff --git a/src/utils/hooks/index.ts b/src/utils/hooks/index.ts index 43891c46be..cf49c0c5a7 100644 --- a/src/utils/hooks/index.ts +++ b/src/utils/hooks/index.ts @@ -1,6 +1,7 @@ export * from './useAppDispatch'; export * from './useAppSelector'; export * from './useDeeplinks'; +export * from './useFontScale'; export * from './useLogger'; export * from './useMount'; export * from './useSensitiveRefClear'; diff --git a/src/utils/hooks/useFontScale.ts b/src/utils/hooks/useFontScale.ts new file mode 100644 index 0000000000..4fed438d3c --- /dev/null +++ b/src/utils/hooks/useFontScale.ts @@ -0,0 +1,12 @@ +import {useWindowDimensions} from 'react-native'; + +/** + * Past this multiplier a dense multi-column row can no longer fit horizontally + * and has to stack. Roughly iOS' first accessibility content size. + */ +export const STACKED_LAYOUT_FONT_SCALE = 1.5; + +export const useFontScale = () => useWindowDimensions().fontScale; + +export const useIsLargeFont = (threshold = STACKED_LAYOUT_FONT_SCALE) => + useWindowDimensions().fontScale >= threshold;