-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
5b44a9f
commit c81acb1
Showing
8 changed files
with
249 additions
and
0 deletions.
There are no files selected for viewing
85 changes: 85 additions & 0 deletions
85
apps/mobile/src/components/amount-field/amount-field-primary-value.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import { useState } from 'react'; | ||
import { NativeSyntheticEvent, TextLayoutEventData } from 'react-native'; | ||
|
||
import { Box, Text, TextProps, Theme } from '@leather.io/ui/native'; | ||
|
||
const maxFontSize = 44; | ||
const lineHeightRatio = 1; | ||
|
||
const commonTextProps: TextProps = { | ||
variant: 'heading02', | ||
fontVariant: ['tabular-nums'], | ||
letterSpacing: 1, | ||
}; | ||
|
||
interface TextMeasurementProxyProps { | ||
value: string; | ||
textProps: TextProps; | ||
onFontSizeChange(fontSize: number): void; | ||
} | ||
|
||
// The Text component's `adjustFontSizeTofit` is not animatable. It also causes text to shift | ||
// due to dynamic positioning issue: https://github.com/facebook/react-native/issues/29507 | ||
// Use an invisible placeholder to extract the dynamic font size set by `adjustFontSizeToFit` | ||
// as the text changes during typing. | ||
function TextMeasurementProxy({ value, textProps, onFontSizeChange }: TextMeasurementProxyProps) { | ||
const handleLayout = (event: NativeSyntheticEvent<TextLayoutEventData>) => { | ||
const line = event.nativeEvent.lines[0]; | ||
if (line) { | ||
onFontSizeChange(line.ascender); | ||
} | ||
}; | ||
|
||
return ( | ||
<Text | ||
variant="heading02" | ||
fontVariant={['tabular-nums']} | ||
letterSpacing={1} | ||
onTextLayout={handleLayout} | ||
adjustsFontSizeToFit | ||
numberOfLines={1} | ||
aria-hidden={true} | ||
style={{ | ||
flexGrow: 1, | ||
opacity: 0, | ||
}} | ||
{...textProps} | ||
> | ||
{value} | ||
</Text> | ||
); | ||
} | ||
|
||
interface AmountFieldPrimaryValueProps { | ||
color: keyof Theme['colors']; | ||
children: string; | ||
} | ||
|
||
export function AmountFieldPrimaryValue({ children, color }: AmountFieldPrimaryValueProps) { | ||
const [dynamicFontSize, setDynamicFontSize] = useState(maxFontSize); | ||
// Maintain the relative lineHeight to prevent text shifting down as font size decreases | ||
const staticLineHeight = maxFontSize * lineHeightRatio; | ||
const dynamicLineHeight = dynamicFontSize * lineHeightRatio; | ||
|
||
return ( | ||
<Box height={staticLineHeight} flexShrink={1}> | ||
<Box flexDirection="row" position="absolute" top={3}> | ||
{children.split('').map((character, index) => ( | ||
<Text | ||
color={color} | ||
key={index} | ||
style={{ fontSize: dynamicFontSize, lineHeight: dynamicLineHeight }} | ||
{...commonTextProps} | ||
> | ||
{character} | ||
</Text> | ||
))} | ||
</Box> | ||
<TextMeasurementProxy | ||
value={children} | ||
onFontSizeChange={setDynamicFontSize} | ||
textProps={commonTextProps} | ||
/> | ||
</Box> | ||
); | ||
} |
26 changes: 26 additions & 0 deletions
26
apps/mobile/src/components/amount-field/amount-field-secondary-value.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { useTheme } from '@shopify/restyle'; | ||
|
||
import { ArrowTopBottomIcon, Box, Pressable, Text, Theme } from '@leather.io/ui/native'; | ||
|
||
interface AmountFieldSecondaryValueProps { | ||
children: string; | ||
onToggleCurrencyMode(): void; | ||
} | ||
|
||
export function AmountFieldSecondaryValue({ | ||
children, | ||
onToggleCurrencyMode, | ||
}: AmountFieldSecondaryValueProps) { | ||
const theme = useTheme<Theme>(); | ||
|
||
return ( | ||
<Pressable hitSlop={16} onPress={onToggleCurrencyMode}> | ||
<Box flexDirection="row" gap="1" alignItems="center"> | ||
<Text variant="label02" color="ink.text-subdued" numberOfLines={1} ellipsizeMode="clip"> | ||
{children} | ||
</Text> | ||
<ArrowTopBottomIcon color={theme.colors['ink.text-subdued']} variant="small" /> | ||
</Box> | ||
</Pressable> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import { AmountSendMaxButton } from '@/components/amount-field/amount-send-max-button'; | ||
|
||
import { Box, Theme } from '@leather.io/ui/native'; | ||
|
||
import { AmountFieldPrimaryValue } from './amount-field-primary-value'; | ||
import { AmountFieldSecondaryValue } from './amount-field-secondary-value'; | ||
|
||
type CurrencyMode = 'crypto' | 'fiat'; // TODO: Should be moved into containing form types | ||
type InternalState = 'initial' | 'active' | 'invalid'; | ||
|
||
interface AmountFieldProps { | ||
inputValue: string; | ||
invalid: boolean; | ||
currencyMode: CurrencyMode; | ||
onCurrencyModeChange(currencyMode: CurrencyMode, newInputValue: string): void; | ||
onSetIsSendingMax(): void; | ||
formatValue(value: string, mode: CurrencyMode): string; | ||
calculateSecondaryValue(value: string, currencyMode: CurrencyMode): string; | ||
} | ||
|
||
export function AmountField({ | ||
inputValue, | ||
currencyMode, | ||
invalid, | ||
onCurrencyModeChange, | ||
onSetIsSendingMax, | ||
calculateSecondaryValue, | ||
formatValue, | ||
}: AmountFieldProps) { | ||
const state = evaluateInternalState({ inputValue, invalid }); | ||
const textColor = getTextColor(state); | ||
const currency = { | ||
primary: currencyMode, | ||
secondary: currencyMode === 'crypto' ? 'fiat' : 'crypto', | ||
} as const; | ||
const amount = { | ||
primary: inputValue, | ||
secondary: calculateSecondaryValue(inputValue, currencyMode), | ||
}; | ||
|
||
function onToggleCurrencyMode() { | ||
onCurrencyModeChange(currency.secondary, amount.secondary); | ||
} | ||
|
||
return ( | ||
<Box | ||
borderColor="ink.border-default" | ||
borderBottomStartRadius="sm" | ||
borderBottomEndRadius="sm" | ||
borderWidth={1} | ||
gap="2" | ||
p="4" | ||
> | ||
<Box flexDirection="row" gap="4" justifyContent="space-between"> | ||
<AmountFieldPrimaryValue color={textColor}> | ||
{formatValue(amount.primary, currency.primary)} | ||
</AmountFieldPrimaryValue> | ||
<AmountSendMaxButton onPress={onSetIsSendingMax} /> | ||
</Box> | ||
<AmountFieldSecondaryValue onToggleCurrencyMode={onToggleCurrencyMode}> | ||
{formatValue(amount.secondary, currency.secondary)} | ||
</AmountFieldSecondaryValue> | ||
</Box> | ||
); | ||
} | ||
|
||
type EvaluateInternalStateParams = Pick<AmountFieldProps, 'invalid' | 'inputValue'>; | ||
|
||
function evaluateInternalState({ | ||
invalid, | ||
inputValue, | ||
}: EvaluateInternalStateParams): InternalState { | ||
if (invalid) { | ||
return 'invalid'; | ||
} | ||
|
||
if (inputValue !== '0') { | ||
return 'active'; | ||
} | ||
|
||
return 'initial'; | ||
} | ||
|
||
function getTextColor(state: InternalState): keyof Theme['colors'] { | ||
const colors: Record<InternalState, keyof Theme['colors']> = { | ||
initial: 'ink.text-subdued', | ||
active: 'ink.text-primary', | ||
invalid: 'red.action-primary-default', | ||
}; | ||
|
||
return colors[state]; | ||
} |
20 changes: 20 additions & 0 deletions
20
apps/mobile/src/components/amount-field/amount-send-max-button.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { t } from '@lingui/macro'; | ||
|
||
import { Pressable, Text } from '@leather.io/ui/native'; | ||
|
||
interface AmountSendMaxButtonProps { | ||
onPress(): void; | ||
} | ||
|
||
export function AmountSendMaxButton({ onPress }: AmountSendMaxButtonProps) { | ||
return ( | ||
<Pressable hitSlop={16} onPress={onPress}> | ||
<Text variant="label02" textTransform="uppercase"> | ||
{t({ | ||
id: 'send_form.max_label', | ||
message: 'Max', | ||
})} | ||
</Text> | ||
</Pressable> | ||
); | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { Component, forwardRef } from 'react'; | ||
|
||
import ArrowTopBottomSmall from '../assets/icons/arrow-top-bottom-16-16.svg'; | ||
import ArrowTopBottom from '../assets/icons/arrow-top-bottom-24-24.svg'; | ||
import { Icon, IconProps } from './icon/icon.native'; | ||
|
||
export const ArrowTopBottomIcon = forwardRef<Component, IconProps>(({ variant, ...props }, ref) => { | ||
if (variant === 'small') | ||
return ( | ||
<Icon ref={ref} {...props}> | ||
<ArrowTopBottomSmall /> | ||
</Icon> | ||
); | ||
return ( | ||
<Icon ref={ref} {...props}> | ||
<ArrowTopBottom /> | ||
</Icon> | ||
); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters