@ledgerhq/lumen-ui-rnative 0.1.40 → 0.1.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/module/lib/Components/AmountInput/AmountInput.js +49 -159
- package/dist/module/lib/Components/AmountInput/AmountInput.js.map +1 -1
- package/dist/module/lib/Components/AmountInput/useAmountInputAnimations/useAmountInputAnimations.js +149 -0
- package/dist/module/lib/Components/AmountInput/useAmountInputAnimations/useAmountInputAnimations.js.map +1 -0
- package/dist/module/lib/Components/AmountInput/useAmountInputFormatting/useAmountInputFormatting.js +22 -0
- package/dist/module/lib/Components/AmountInput/useAmountInputFormatting/useAmountInputFormatting.js.map +1 -0
- package/dist/module/lib/Components/AmountInput/useAmountInputFormatting/useAmountInputFormatting.test.js +59 -0
- package/dist/module/lib/Components/AmountInput/useAmountInputFormatting/useAmountInputFormatting.test.js.map +1 -0
- package/dist/module/lib/Components/Banner/Banner.js +5 -9
- package/dist/module/lib/Components/Banner/Banner.js.map +1 -1
- package/dist/module/lib/Components/BottomSheet/BottomSheetHeader.js +0 -3
- package/dist/module/lib/Components/BottomSheet/BottomSheetHeader.js.map +1 -1
- package/dist/module/lib/Components/Icon/Icon.test.js +1 -1
- package/dist/module/lib/Components/SegmentedControl/SegmentedControl.test.js +162 -66
- package/dist/module/lib/Components/SegmentedControl/SegmentedControl.test.js.map +1 -1
- package/dist/module/lib/Components/SegmentedControl/usePillLayout.js +19 -15
- package/dist/module/lib/Components/SegmentedControl/usePillLayout.js.map +1 -1
- package/dist/module/lib/Components/Trend/Trend.js +19 -16
- package/dist/module/lib/Components/Trend/Trend.js.map +1 -1
- package/dist/typescript/src/lib/Components/AmountInput/AmountInput.d.ts +1 -1
- package/dist/typescript/src/lib/Components/AmountInput/AmountInput.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/AmountInput/useAmountInputAnimations/useAmountInputAnimations.d.ts +16 -0
- package/dist/typescript/src/lib/Components/AmountInput/useAmountInputAnimations/useAmountInputAnimations.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/AmountInput/useAmountInputFormatting/useAmountInputFormatting.d.ts +18 -0
- package/dist/typescript/src/lib/Components/AmountInput/useAmountInputFormatting/useAmountInputFormatting.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/Banner/Banner.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/Banner/types.d.ts +3 -3
- package/dist/typescript/src/lib/Components/Banner/types.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/BottomSheet/BottomSheetHeader.d.ts +1 -1
- package/dist/typescript/src/lib/Components/BottomSheet/BottomSheetHeader.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/SegmentedControl/usePillLayout.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/Trend/Trend.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/lib/Components/AmountInput/AmountInput.tsx +55 -115
- package/src/lib/Components/AmountInput/useAmountInputAnimations/useAmountInputAnimations.ts +100 -0
- package/src/lib/Components/AmountInput/useAmountInputFormatting/useAmountInputFormatting.test.ts +62 -0
- package/src/lib/Components/AmountInput/useAmountInputFormatting/useAmountInputFormatting.ts +48 -0
- package/src/lib/Components/Banner/Banner.tsx +8 -11
- package/src/lib/Components/Banner/types.ts +3 -3
- package/src/lib/Components/BottomSheet/BottomSheetHeader.tsx +0 -4
- package/src/lib/Components/Icon/Icon.test.tsx +1 -1
- package/src/lib/Components/SegmentedControl/SegmentedControl.test.tsx +152 -60
- package/src/lib/Components/SegmentedControl/usePillLayout.ts +21 -12
- package/src/lib/Components/Trend/Trend.tsx +24 -22
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Trend.d.ts","sourceRoot":"","sources":["../../../../../../src/lib/Components/Trend/Trend.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"Trend.d.ts","sourceRoot":"","sources":["../../../../../../src/lib/Components/Trend/Trend.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AA4B1C,wBAAgB,KAAK,CAAC,EACpB,KAAK,EACL,IAAW,EACX,EAAO,EACP,QAAQ,EAAE,YAAoB,EAC9B,KAAK,EACL,GAAG,KAAK,EACT,EAAE,UAAU,2CAoCZ"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ledgerhq/lumen-ui-rnative",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.42",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react-native",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"peerDependencies": {
|
|
53
53
|
"@types/react": "^19.0.0",
|
|
54
54
|
"@gorhom/bottom-sheet": "^5.0.0",
|
|
55
|
-
"@ledgerhq/lumen-design-core": "0.1.
|
|
55
|
+
"@ledgerhq/lumen-design-core": "0.1.17",
|
|
56
56
|
"react": "^19.0.0",
|
|
57
57
|
"react-native": "~0.79.7",
|
|
58
58
|
"react-native-reanimated": "^4.1.0",
|
|
@@ -1,25 +1,28 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
useDisabledContext,
|
|
5
|
-
} from '@ledgerhq/lumen-utils-shared';
|
|
6
|
-
import { useEffect, useImperativeHandle, useRef, useState } from 'react';
|
|
1
|
+
import { useDisabledContext } from '@ledgerhq/lumen-utils-shared';
|
|
2
|
+
import { useImperativeHandle, useRef, useState } from 'react';
|
|
3
|
+
import type { StyleProp, TextStyle } from 'react-native';
|
|
7
4
|
import { Pressable, StyleSheet, TextInput, View } from 'react-native';
|
|
8
|
-
import Animated
|
|
9
|
-
|
|
10
|
-
useAnimatedStyle,
|
|
11
|
-
useSharedValue,
|
|
12
|
-
withRepeat,
|
|
13
|
-
withSequence,
|
|
14
|
-
withTiming,
|
|
15
|
-
} from 'react-native-reanimated';
|
|
16
|
-
import { useStyleSheet, useTheme } from '../../../styles';
|
|
5
|
+
import Animated from 'react-native-reanimated';
|
|
6
|
+
import { useStyleSheet } from '../../../styles';
|
|
17
7
|
import { Box } from '../Utility';
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
8
|
+
import type {
|
|
9
|
+
AmountInputAlign,
|
|
10
|
+
AmountInputProps,
|
|
11
|
+
AmountInputSize,
|
|
22
12
|
} from './types';
|
|
13
|
+
import { useAmountInputAnimations } from './useAmountInputAnimations/useAmountInputAnimations';
|
|
14
|
+
import { useAmountInputFormatting } from './useAmountInputFormatting/useAmountInputFormatting';
|
|
15
|
+
|
|
16
|
+
type CurrencyProps = {
|
|
17
|
+
style: StyleProp<TextStyle>;
|
|
18
|
+
children: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const Currency = ({ style, children }: CurrencyProps) => (
|
|
22
|
+
<Animated.Text style={style} allowFontScaling={false}>
|
|
23
|
+
{children}
|
|
24
|
+
</Animated.Text>
|
|
25
|
+
);
|
|
23
26
|
|
|
24
27
|
/**
|
|
25
28
|
* AmountInput component for handling numeric input with currency display.
|
|
@@ -46,114 +49,43 @@ export const AmountInput = ({
|
|
|
46
49
|
...props
|
|
47
50
|
}: AmountInputProps) => {
|
|
48
51
|
const inputRef = useRef<TextInput>(null);
|
|
49
|
-
const inputValue = textFormatter(String(value), {
|
|
50
|
-
allowDecimals,
|
|
51
|
-
thousandsSeparator,
|
|
52
|
-
maxIntegerLength,
|
|
53
|
-
maxDecimalLength,
|
|
54
|
-
});
|
|
55
52
|
const [isFocused, setIsFocused] = useState(false);
|
|
53
|
+
|
|
56
54
|
const disabled = useDisabledContext({
|
|
57
55
|
consumerName: 'AmountInput',
|
|
58
56
|
mergeWith: { disabled: disabledProp },
|
|
59
57
|
});
|
|
60
58
|
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
const { formattedValue, handleChangeText } = useAmountInputFormatting({
|
|
60
|
+
value,
|
|
61
|
+
onChangeText,
|
|
62
|
+
formatOptions: {
|
|
63
|
+
allowDecimals,
|
|
64
|
+
thousandsSeparator,
|
|
65
|
+
maxIntegerLength,
|
|
66
|
+
maxDecimalLength,
|
|
67
|
+
},
|
|
68
|
+
});
|
|
64
69
|
|
|
65
|
-
|
|
70
|
+
const { animatedInputStyle, animatedCurrencyStyle, animatedCaretStyle } =
|
|
71
|
+
useAmountInputAnimations({
|
|
72
|
+
formattedValue,
|
|
73
|
+
size,
|
|
74
|
+
isFocused,
|
|
75
|
+
disabled,
|
|
76
|
+
});
|
|
66
77
|
|
|
67
|
-
const { theme } = useTheme();
|
|
68
78
|
const styles = useStyles({
|
|
69
79
|
size,
|
|
70
80
|
align,
|
|
71
|
-
hasValue: !!
|
|
81
|
+
hasValue: !!formattedValue,
|
|
72
82
|
isEditable: !disabled,
|
|
73
83
|
isInvalid,
|
|
74
84
|
});
|
|
75
|
-
const caretFixedHeight = size === 'sm' ? theme.sizes.s28 : 0;
|
|
76
|
-
|
|
77
|
-
const animatedInputStyle = useAnimatedStyle(
|
|
78
|
-
() => ({
|
|
79
|
-
transform: [{ translateX: translateX.value }],
|
|
80
|
-
fontSize: animatedFontSize.value,
|
|
81
|
-
letterSpacing: 0,
|
|
82
|
-
}),
|
|
83
|
-
[translateX, animatedFontSize],
|
|
84
|
-
);
|
|
85
|
-
|
|
86
|
-
const animatedCurrencyStyle = useAnimatedStyle(
|
|
87
|
-
() => ({
|
|
88
|
-
fontSize: animatedFontSize.value,
|
|
89
|
-
letterSpacing: 0,
|
|
90
|
-
}),
|
|
91
|
-
[animatedFontSize],
|
|
92
|
-
);
|
|
93
|
-
|
|
94
|
-
const animatedCaretStyle = useAnimatedStyle(
|
|
95
|
-
() => ({
|
|
96
|
-
opacity: caretOpacity.value,
|
|
97
|
-
height: size === 'sm' ? caretFixedHeight : animatedFontSize.value,
|
|
98
|
-
}),
|
|
99
|
-
[caretOpacity, animatedFontSize, size, caretFixedHeight],
|
|
100
|
-
);
|
|
101
|
-
|
|
102
|
-
useEffect(() => {
|
|
103
|
-
const newSize = getFontSize(inputValue, size);
|
|
104
|
-
|
|
105
|
-
translateX.value = withSequence(
|
|
106
|
-
withTiming(4, { duration: 0 }),
|
|
107
|
-
withTiming(0, {
|
|
108
|
-
duration: 250,
|
|
109
|
-
easing: Easing.bezier(0.4, 0, 0.2, 1),
|
|
110
|
-
}),
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
animatedFontSize.value = withTiming(newSize, {
|
|
114
|
-
duration: 250,
|
|
115
|
-
easing: Easing.bezier(0.4, 0, 0.2, 1),
|
|
116
|
-
});
|
|
117
|
-
}, [inputValue, size, animatedFontSize, translateX]);
|
|
118
|
-
|
|
119
|
-
useEffect(() => {
|
|
120
|
-
if (isFocused && !disabled) {
|
|
121
|
-
caretOpacity.value = withRepeat(
|
|
122
|
-
withSequence(
|
|
123
|
-
withTiming(1, { duration: 150, easing: Easing.ease }),
|
|
124
|
-
withTiming(1, { duration: 500 }),
|
|
125
|
-
withTiming(0, { duration: 150, easing: Easing.ease }),
|
|
126
|
-
withTiming(0, { duration: 500 }),
|
|
127
|
-
),
|
|
128
|
-
-1,
|
|
129
|
-
false,
|
|
130
|
-
);
|
|
131
|
-
} else {
|
|
132
|
-
caretOpacity.value = 0;
|
|
133
|
-
}
|
|
134
|
-
}, [isFocused, disabled, caretOpacity]);
|
|
135
85
|
|
|
136
|
-
|
|
137
|
-
const formatted = textFormatter(text, {
|
|
138
|
-
allowDecimals,
|
|
139
|
-
thousandsSeparator,
|
|
140
|
-
maxIntegerLength,
|
|
141
|
-
maxDecimalLength,
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
onChangeText(formatted);
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
const CurrencyText = currencyText ? (
|
|
148
|
-
<Animated.Text
|
|
149
|
-
style={[styles.currency, animatedCurrencyStyle]}
|
|
150
|
-
allowFontScaling={false}
|
|
151
|
-
>
|
|
152
|
-
{currencyText}
|
|
153
|
-
</Animated.Text>
|
|
154
|
-
) : null;
|
|
86
|
+
useImperativeHandle(ref, () => inputRef.current as TextInput, []);
|
|
155
87
|
|
|
156
|
-
const handlePress = () => {
|
|
88
|
+
const handlePress = (): void => {
|
|
157
89
|
if (!disabled) {
|
|
158
90
|
inputRef.current?.focus();
|
|
159
91
|
}
|
|
@@ -166,7 +98,7 @@ export const AmountInput = ({
|
|
|
166
98
|
ref={inputRef}
|
|
167
99
|
keyboardType='decimal-pad'
|
|
168
100
|
editable={editable !== false && !disabled}
|
|
169
|
-
value={
|
|
101
|
+
value={formattedValue}
|
|
170
102
|
onChangeText={handleChangeText}
|
|
171
103
|
onFocus={(e) => {
|
|
172
104
|
setIsFocused(true);
|
|
@@ -185,20 +117,28 @@ export const AmountInput = ({
|
|
|
185
117
|
style={styles.pressable}
|
|
186
118
|
accessibilityLabel={props.accessibilityLabel || 'Amount input'}
|
|
187
119
|
>
|
|
188
|
-
{currencyPosition === 'left' &&
|
|
120
|
+
{currencyText && currencyPosition === 'left' && (
|
|
121
|
+
<Currency style={[styles.currency, animatedCurrencyStyle]}>
|
|
122
|
+
{currencyText}
|
|
123
|
+
</Currency>
|
|
124
|
+
)}
|
|
189
125
|
|
|
190
126
|
{/** display text that mirrors the hidden input's value */}
|
|
191
127
|
<Animated.Text
|
|
192
128
|
style={[styles.displayText, animatedInputStyle, style]}
|
|
193
129
|
allowFontScaling={false}
|
|
194
130
|
>
|
|
195
|
-
{
|
|
131
|
+
{formattedValue || '0'}
|
|
196
132
|
</Animated.Text>
|
|
197
133
|
|
|
198
134
|
{/** custom caret */}
|
|
199
135
|
<Animated.View style={[styles.caret, animatedCaretStyle]} />
|
|
200
136
|
|
|
201
|
-
{currencyPosition === 'right' &&
|
|
137
|
+
{currencyText && currencyPosition === 'right' && (
|
|
138
|
+
<Currency style={[styles.currency, animatedCurrencyStyle]}>
|
|
139
|
+
{currencyText}
|
|
140
|
+
</Currency>
|
|
141
|
+
)}
|
|
202
142
|
</Pressable>
|
|
203
143
|
</View>
|
|
204
144
|
</Box>
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { getFontSize } from '@ledgerhq/lumen-utils-shared';
|
|
2
|
+
import { useEffect } from 'react';
|
|
3
|
+
import {
|
|
4
|
+
Easing,
|
|
5
|
+
useAnimatedStyle,
|
|
6
|
+
useSharedValue,
|
|
7
|
+
withRepeat,
|
|
8
|
+
withSequence,
|
|
9
|
+
withTiming,
|
|
10
|
+
} from 'react-native-reanimated';
|
|
11
|
+
import { useTheme } from '../../../../styles';
|
|
12
|
+
import type { AmountInputSize } from '../types';
|
|
13
|
+
|
|
14
|
+
type UseAmountInputAnimationsArgs = {
|
|
15
|
+
formattedValue: string;
|
|
16
|
+
size: AmountInputSize;
|
|
17
|
+
isFocused: boolean;
|
|
18
|
+
disabled: boolean;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type UseAmountInputAnimationsReturn = {
|
|
22
|
+
animatedInputStyle: ReturnType<typeof useAnimatedStyle>;
|
|
23
|
+
animatedCurrencyStyle: ReturnType<typeof useAnimatedStyle>;
|
|
24
|
+
animatedCaretStyle: ReturnType<typeof useAnimatedStyle>;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const useAmountInputAnimations = ({
|
|
28
|
+
formattedValue,
|
|
29
|
+
size,
|
|
30
|
+
isFocused,
|
|
31
|
+
disabled,
|
|
32
|
+
}: UseAmountInputAnimationsArgs): UseAmountInputAnimationsReturn => {
|
|
33
|
+
const { theme } = useTheme();
|
|
34
|
+
const caretFixedHeight = size === 'sm' ? theme.sizes.s28 : 0;
|
|
35
|
+
|
|
36
|
+
const translateX = useSharedValue(0);
|
|
37
|
+
const animatedFontSize = useSharedValue(getFontSize(formattedValue, size));
|
|
38
|
+
const caretOpacity = useSharedValue(0);
|
|
39
|
+
|
|
40
|
+
const animatedInputStyle = useAnimatedStyle(
|
|
41
|
+
() => ({
|
|
42
|
+
transform: [{ translateX: translateX.value }],
|
|
43
|
+
fontSize: animatedFontSize.value,
|
|
44
|
+
letterSpacing: 0,
|
|
45
|
+
}),
|
|
46
|
+
[translateX, animatedFontSize],
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const animatedCurrencyStyle = useAnimatedStyle(
|
|
50
|
+
() => ({
|
|
51
|
+
fontSize: animatedFontSize.value,
|
|
52
|
+
letterSpacing: 0,
|
|
53
|
+
}),
|
|
54
|
+
[animatedFontSize],
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const animatedCaretStyle = useAnimatedStyle(
|
|
58
|
+
() => ({
|
|
59
|
+
opacity: caretOpacity.value,
|
|
60
|
+
height: size === 'sm' ? caretFixedHeight : animatedFontSize.value,
|
|
61
|
+
}),
|
|
62
|
+
[caretOpacity, animatedFontSize, size, caretFixedHeight],
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
const newSize = getFontSize(formattedValue, size);
|
|
67
|
+
|
|
68
|
+
translateX.value = withSequence(
|
|
69
|
+
withTiming(4, { duration: 0 }),
|
|
70
|
+
withTiming(0, {
|
|
71
|
+
duration: 250,
|
|
72
|
+
easing: Easing.bezier(0.4, 0, 0.2, 1),
|
|
73
|
+
}),
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
animatedFontSize.value = withTiming(newSize, {
|
|
77
|
+
duration: 250,
|
|
78
|
+
easing: Easing.bezier(0.4, 0, 0.2, 1),
|
|
79
|
+
});
|
|
80
|
+
}, [formattedValue, size, animatedFontSize, translateX]);
|
|
81
|
+
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
if (isFocused && !disabled) {
|
|
84
|
+
caretOpacity.value = withRepeat(
|
|
85
|
+
withSequence(
|
|
86
|
+
withTiming(1, { duration: 150, easing: Easing.ease }),
|
|
87
|
+
withTiming(1, { duration: 500 }),
|
|
88
|
+
withTiming(0, { duration: 150, easing: Easing.ease }),
|
|
89
|
+
withTiming(0, { duration: 500 }),
|
|
90
|
+
),
|
|
91
|
+
-1,
|
|
92
|
+
false,
|
|
93
|
+
);
|
|
94
|
+
} else {
|
|
95
|
+
caretOpacity.value = 0;
|
|
96
|
+
}
|
|
97
|
+
}, [isFocused, disabled, caretOpacity]);
|
|
98
|
+
|
|
99
|
+
return { animatedInputStyle, animatedCurrencyStyle, animatedCaretStyle };
|
|
100
|
+
};
|
package/src/lib/Components/AmountInput/useAmountInputFormatting/useAmountInputFormatting.test.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { describe, it, expect, jest } from '@jest/globals';
|
|
2
|
+
import { renderHook, act } from '@testing-library/react-native';
|
|
3
|
+
import { useAmountInputFormatting } from './useAmountInputFormatting';
|
|
4
|
+
|
|
5
|
+
const defaultFormatOptions = {
|
|
6
|
+
allowDecimals: true,
|
|
7
|
+
thousandsSeparator: true,
|
|
8
|
+
maxIntegerLength: 9,
|
|
9
|
+
maxDecimalLength: 9,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
describe('useAmountInputFormatting', () => {
|
|
13
|
+
it('formats the value prop', () => {
|
|
14
|
+
const { result } = renderHook(() =>
|
|
15
|
+
useAmountInputFormatting({
|
|
16
|
+
value: '1000',
|
|
17
|
+
onChangeText: jest.fn(),
|
|
18
|
+
formatOptions: defaultFormatOptions,
|
|
19
|
+
}),
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
expect(result.current.formattedValue).toBe('1 000');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('updates formattedValue when the value prop changes', () => {
|
|
26
|
+
const { result, rerender } = renderHook<
|
|
27
|
+
ReturnType<typeof useAmountInputFormatting>,
|
|
28
|
+
{ value: string }
|
|
29
|
+
>(
|
|
30
|
+
({ value }) =>
|
|
31
|
+
useAmountInputFormatting({
|
|
32
|
+
value,
|
|
33
|
+
onChangeText: jest.fn(),
|
|
34
|
+
formatOptions: defaultFormatOptions,
|
|
35
|
+
}),
|
|
36
|
+
{ initialProps: { value: '100' } },
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
expect(result.current.formattedValue).toBe('100');
|
|
40
|
+
|
|
41
|
+
rerender({ value: '2000' });
|
|
42
|
+
|
|
43
|
+
expect(result.current.formattedValue).toBe('2 000');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('formats user input and calls onChangeText with the cleaned value', () => {
|
|
47
|
+
const onChangeText = jest.fn();
|
|
48
|
+
const { result } = renderHook(() =>
|
|
49
|
+
useAmountInputFormatting({
|
|
50
|
+
value: '',
|
|
51
|
+
onChangeText,
|
|
52
|
+
formatOptions: defaultFormatOptions,
|
|
53
|
+
}),
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
act(() => {
|
|
57
|
+
result.current.handleChangeText('1000');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
expect(onChangeText).toHaveBeenCalledWith('1 000');
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { textFormatter } from '@ledgerhq/lumen-utils-shared';
|
|
2
|
+
import { useCallback, useMemo } from 'react';
|
|
3
|
+
|
|
4
|
+
type FormatOptions = {
|
|
5
|
+
allowDecimals: boolean;
|
|
6
|
+
thousandsSeparator: boolean;
|
|
7
|
+
maxIntegerLength: number;
|
|
8
|
+
maxDecimalLength: number;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
type UseAmountInputFormattingArgs = {
|
|
12
|
+
value: string | number;
|
|
13
|
+
onChangeText: (text: string) => void;
|
|
14
|
+
formatOptions: FormatOptions;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type UseAmountInputFormattingReturn = {
|
|
18
|
+
formattedValue: string;
|
|
19
|
+
handleChangeText: (text: string) => void;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const useAmountInputFormatting = ({
|
|
23
|
+
value,
|
|
24
|
+
onChangeText,
|
|
25
|
+
formatOptions,
|
|
26
|
+
}: UseAmountInputFormattingArgs): UseAmountInputFormattingReturn => {
|
|
27
|
+
const format = useCallback(
|
|
28
|
+
(v: string | number): string => textFormatter(String(v), formatOptions),
|
|
29
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
30
|
+
[
|
|
31
|
+
formatOptions.allowDecimals,
|
|
32
|
+
formatOptions.thousandsSeparator,
|
|
33
|
+
formatOptions.maxIntegerLength,
|
|
34
|
+
formatOptions.maxDecimalLength,
|
|
35
|
+
],
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const formattedValue = useMemo(() => format(value), [value, format]);
|
|
39
|
+
|
|
40
|
+
const handleChangeText = useCallback(
|
|
41
|
+
(text: string): void => {
|
|
42
|
+
onChangeText(format(text));
|
|
43
|
+
},
|
|
44
|
+
[format, onChangeText],
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
return { formattedValue, handleChangeText };
|
|
48
|
+
};
|
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
import type { IconProps } from '../Icon';
|
|
14
14
|
import { IconButton } from '../IconButton';
|
|
15
15
|
import { Box } from '../Utility';
|
|
16
|
-
import { Wrap } from '../Wrap';
|
|
17
16
|
import type { BannerProps } from './types';
|
|
18
17
|
|
|
19
18
|
type Appearance = NonNullable<BannerProps['appearance']>;
|
|
@@ -82,6 +81,7 @@ const useStyles = ({ appearance }: { appearance: Appearance }) => {
|
|
|
82
81
|
]),
|
|
83
82
|
actionsWrapper: {
|
|
84
83
|
flexDirection: 'row',
|
|
84
|
+
flexWrap: 'wrap',
|
|
85
85
|
gap: t.spacings.s4,
|
|
86
86
|
},
|
|
87
87
|
};
|
|
@@ -169,16 +169,13 @@ export const Banner = ({
|
|
|
169
169
|
)}
|
|
170
170
|
{description && (
|
|
171
171
|
<View>
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
>
|
|
180
|
-
{description}
|
|
181
|
-
</Wrap>
|
|
172
|
+
{isTextChildren(description) ? (
|
|
173
|
+
<Text style={styles.description} numberOfLines={5}>
|
|
174
|
+
{description}
|
|
175
|
+
</Text>
|
|
176
|
+
) : (
|
|
177
|
+
description
|
|
178
|
+
)}
|
|
182
179
|
</View>
|
|
183
180
|
)}
|
|
184
181
|
</View>
|
|
@@ -9,7 +9,7 @@ export type BannerProps = {
|
|
|
9
9
|
/**
|
|
10
10
|
* The main title of the banner.
|
|
11
11
|
*/
|
|
12
|
-
title?:
|
|
12
|
+
title?: ReactNode;
|
|
13
13
|
/**
|
|
14
14
|
* Optional descriptive text.
|
|
15
15
|
*/
|
|
@@ -17,11 +17,11 @@ export type BannerProps = {
|
|
|
17
17
|
/**
|
|
18
18
|
* Optional primary action.
|
|
19
19
|
*/
|
|
20
|
-
primaryAction?:
|
|
20
|
+
primaryAction?: ReactNode;
|
|
21
21
|
/**
|
|
22
22
|
* Optional secondary action.
|
|
23
23
|
*/
|
|
24
|
-
secondaryAction?:
|
|
24
|
+
secondaryAction?: ReactNode;
|
|
25
25
|
/**
|
|
26
26
|
* Optional close action.
|
|
27
27
|
*/
|
|
@@ -119,10 +119,6 @@ export const BottomSheetHeader = ({
|
|
|
119
119
|
hidden: !hasIcons && density !== 'compact',
|
|
120
120
|
});
|
|
121
121
|
|
|
122
|
-
if (!hasTitleSection && !onBack && hideCloseButton) {
|
|
123
|
-
return null;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
122
|
const titleComponent = hasTitleSection ? (
|
|
127
123
|
<Box style={styles.textWrapper}>
|
|
128
124
|
{title && (
|