@ledgerhq/lumen-ui-rnative 0.1.30 → 0.1.32

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.
Files changed (215) hide show
  1. package/dist/module/i18n/i18n.js +5 -1
  2. package/dist/module/i18n/i18n.js.map +1 -1
  3. package/dist/module/i18n/locales/en.json +5 -0
  4. package/dist/module/lib/Components/AddressInput/AddressInput.mdx +10 -12
  5. package/dist/module/lib/Components/AddressInput/AddressInput.stories.js +2 -1
  6. package/dist/module/lib/Components/AddressInput/AddressInput.stories.js.map +1 -1
  7. package/dist/module/lib/Components/AmountInput/AmountInput.mdx +3 -3
  8. package/dist/module/lib/Components/BaseInput/BaseInput.js +60 -32
  9. package/dist/module/lib/Components/BaseInput/BaseInput.js.map +1 -1
  10. package/dist/module/lib/Components/DotCount/DotCount.stories.js +9 -18
  11. package/dist/module/lib/Components/DotCount/DotCount.stories.js.map +1 -1
  12. package/dist/module/lib/Components/DotIndicator/DotIndicator.stories.js +3 -5
  13. package/dist/module/lib/Components/DotIndicator/DotIndicator.stories.js.map +1 -1
  14. package/dist/module/lib/Components/MediaImage/MediaImage.test.js +7 -3
  15. package/dist/module/lib/Components/MediaImage/MediaImage.test.js.map +1 -1
  16. package/dist/module/lib/Components/SearchInput/SearchInput.mdx +4 -10
  17. package/dist/module/lib/Components/SearchInput/SearchInput.stories.js +2 -1
  18. package/dist/module/lib/Components/SearchInput/SearchInput.stories.js.map +1 -1
  19. package/dist/module/lib/Components/SegmentedControl/SegmentedControl.js +2 -1
  20. package/dist/module/lib/Components/SegmentedControl/SegmentedControl.js.map +1 -1
  21. package/dist/module/lib/Components/SegmentedControl/SegmentedControl.mdx +34 -4
  22. package/dist/module/lib/Components/SegmentedControl/SegmentedControl.stories.js +32 -7
  23. package/dist/module/lib/Components/SegmentedControl/SegmentedControl.stories.js.map +1 -1
  24. package/dist/module/lib/Components/SegmentedControl/SegmentedControl.test.js +26 -0
  25. package/dist/module/lib/Components/SegmentedControl/SegmentedControl.test.js.map +1 -1
  26. package/dist/module/lib/Components/TextInput/TextInput.js +4 -3
  27. package/dist/module/lib/Components/TextInput/TextInput.js.map +1 -1
  28. package/dist/module/lib/Components/TextInput/TextInput.mdx +8 -10
  29. package/dist/module/lib/Components/TextInput/TextInput.stories.js +40 -1
  30. package/dist/module/lib/Components/TextInput/TextInput.stories.js.map +1 -1
  31. package/dist/module/lib/Components/TextInput/TextInput.test.js +76 -0
  32. package/dist/module/lib/Components/TextInput/TextInput.test.js.map +1 -0
  33. package/dist/module/lib/Components/Trend/Trend.js +103 -0
  34. package/dist/module/lib/Components/Trend/Trend.js.map +1 -0
  35. package/dist/module/lib/Components/Trend/Trend.mdx +106 -0
  36. package/dist/module/lib/Components/Trend/Trend.stories.js +79 -0
  37. package/dist/module/lib/Components/Trend/Trend.stories.js.map +1 -0
  38. package/dist/module/lib/Components/Trend/Trend.test.js +149 -0
  39. package/dist/module/lib/Components/Trend/Trend.test.js.map +1 -0
  40. package/dist/module/lib/Components/Trend/index.js +5 -0
  41. package/dist/module/lib/Components/Trend/index.js.map +1 -0
  42. package/dist/module/lib/Components/Trend/types.js +4 -0
  43. package/dist/module/lib/Components/Trend/types.js.map +1 -0
  44. package/dist/module/lib/Components/index.js +5 -4
  45. package/dist/module/lib/Components/index.js.map +1 -1
  46. package/dist/module/lib/Symbols/Icons/Ar.js +2 -2
  47. package/dist/module/lib/Symbols/Icons/Ar.js.map +1 -1
  48. package/dist/module/lib/Symbols/Icons/ArrowLeft.js +1 -1
  49. package/dist/module/lib/Symbols/Icons/ArrowLeft.js.map +1 -1
  50. package/dist/module/lib/Symbols/Icons/ArrowRight.js +1 -1
  51. package/dist/module/lib/Symbols/Icons/ArrowRight.js.map +1 -1
  52. package/dist/module/lib/Symbols/Icons/BasketPutIn.js +1 -1
  53. package/dist/module/lib/Symbols/Icons/BasketPutIn.js.map +1 -1
  54. package/dist/module/lib/Symbols/Icons/Clip.js +1 -1
  55. package/dist/module/lib/Symbols/Icons/ClockFill.js +5 -14
  56. package/dist/module/lib/Symbols/Icons/ClockFill.js.map +1 -1
  57. package/dist/module/lib/Symbols/Icons/CloudDownload.js +1 -1
  58. package/dist/module/lib/Symbols/Icons/CloudDownload.js.map +1 -1
  59. package/dist/module/lib/Symbols/Icons/CloudUpload.js +1 -1
  60. package/dist/module/lib/Symbols/Icons/CloudUpload.js.map +1 -1
  61. package/dist/module/lib/Symbols/Icons/CoinsAddPlus.js +1 -1
  62. package/dist/module/lib/Symbols/Icons/CoinsAddPlus.js.map +1 -1
  63. package/dist/module/lib/Symbols/Icons/CoinsCheck.js +1 -1
  64. package/dist/module/lib/Symbols/Icons/CoinsCheck.js.map +1 -1
  65. package/dist/module/lib/Symbols/Icons/CoinsCross.js +1 -1
  66. package/dist/module/lib/Symbols/Icons/CoinsCross.js.map +1 -1
  67. package/dist/module/lib/Symbols/Icons/ColorPalette.js +1 -1
  68. package/dist/module/lib/Symbols/Icons/ColorPalette.js.map +1 -1
  69. package/dist/module/lib/Symbols/Icons/CryptoBitcoinCoin.js +1 -1
  70. package/dist/module/lib/Symbols/Icons/Csv.js +1 -1
  71. package/dist/module/lib/Symbols/Icons/Csv.js.map +1 -1
  72. package/dist/module/lib/Symbols/Icons/Discord.js +1 -1
  73. package/dist/module/lib/Symbols/Icons/Discord.js.map +1 -1
  74. package/dist/module/lib/Symbols/Icons/Download.js +1 -1
  75. package/dist/module/lib/Symbols/Icons/Download.js.map +1 -1
  76. package/dist/module/lib/Symbols/Icons/Exchange.js +1 -1
  77. package/dist/module/lib/Symbols/Icons/Exchange.js.map +1 -1
  78. package/dist/module/lib/Symbols/Icons/ExchangeFill.js +1 -1
  79. package/dist/module/lib/Symbols/Icons/ExchangeFill.js.map +1 -1
  80. package/dist/module/lib/Symbols/Icons/ExitLogout.js +1 -1
  81. package/dist/module/lib/Symbols/Icons/ExitLogout.js.map +1 -1
  82. package/dist/module/lib/Symbols/Icons/Expand.js +1 -1
  83. package/dist/module/lib/Symbols/Icons/Expand.js.map +1 -1
  84. package/dist/module/lib/Symbols/Icons/Experiment2.js +1 -1
  85. package/dist/module/lib/Symbols/Icons/Experiment2.js.map +1 -1
  86. package/dist/module/lib/Symbols/Icons/ExternalLink.js +1 -1
  87. package/dist/module/lib/Symbols/Icons/ExternalLink.js.map +1 -1
  88. package/dist/module/lib/Symbols/Icons/FileDownload.js +1 -1
  89. package/dist/module/lib/Symbols/Icons/FileDownload.js.map +1 -1
  90. package/dist/module/lib/Symbols/Icons/Github.js +1 -1
  91. package/dist/module/lib/Symbols/Icons/HandCard.js +1 -1
  92. package/dist/module/lib/Symbols/Icons/HandCard.js.map +1 -1
  93. package/dist/module/lib/Symbols/Icons/Infinite.js +1 -1
  94. package/dist/module/lib/Symbols/Icons/MobileArrow.js +1 -1
  95. package/dist/module/lib/Symbols/Icons/MobileArrow.js.map +1 -1
  96. package/dist/module/lib/Symbols/Icons/NftHide.js +1 -1
  97. package/dist/module/lib/Symbols/Icons/NftHide.js.map +1 -1
  98. package/dist/module/lib/Symbols/Icons/Parachute.js +1 -1
  99. package/dist/module/lib/Symbols/Icons/Parachute.js.map +1 -1
  100. package/dist/module/lib/Symbols/Icons/PictureImage.js +1 -1
  101. package/dist/module/lib/Symbols/Icons/Range.js +1 -1
  102. package/dist/module/lib/Symbols/Icons/Range.js.map +1 -1
  103. package/dist/module/lib/Symbols/Icons/Reddit.js +2 -2
  104. package/dist/module/lib/Symbols/Icons/Reduce.js +1 -1
  105. package/dist/module/lib/Symbols/Icons/Reduce.js.map +1 -1
  106. package/dist/module/lib/Symbols/Icons/Screens.js +1 -1
  107. package/dist/module/lib/Symbols/Icons/Screens.js.map +1 -1
  108. package/dist/module/lib/Symbols/Icons/Shapes.js +1 -1
  109. package/dist/module/lib/Symbols/Icons/Share.js +1 -1
  110. package/dist/module/lib/Symbols/Icons/Share.js.map +1 -1
  111. package/dist/module/lib/Symbols/Icons/StarHalf.js +1 -1
  112. package/dist/module/lib/Symbols/Icons/StarHalf.js.map +1 -1
  113. package/dist/module/lib/Symbols/Icons/TransferHorizontal.js +1 -1
  114. package/dist/module/lib/Symbols/Icons/TransferHorizontal.js.map +1 -1
  115. package/dist/module/lib/Symbols/Icons/TransferVertical.js +1 -1
  116. package/dist/module/lib/Symbols/Icons/TransferVertical.js.map +1 -1
  117. package/dist/module/lib/Symbols/Icons/Truck.js +1 -1
  118. package/dist/module/lib/Symbols/Icons/Usb.js +1 -1
  119. package/dist/module/lib/Symbols/Icons/Usb.js.map +1 -1
  120. package/dist/module/lib/Symbols/Icons/UserArrowRight.js +1 -1
  121. package/dist/module/lib/Symbols/Icons/UserArrowRight.js.map +1 -1
  122. package/dist/module/lib/Symbols/Icons/WalletInput.js +1 -1
  123. package/dist/module/lib/Symbols/Icons/WalletInput.js.map +1 -1
  124. package/dist/typescript/src/i18n/i18n.d.ts.map +1 -1
  125. package/dist/typescript/src/lib/Components/BaseInput/BaseInput.d.ts +1 -1
  126. package/dist/typescript/src/lib/Components/BaseInput/BaseInput.d.ts.map +1 -1
  127. package/dist/typescript/src/lib/Components/BaseInput/types.d.ts +9 -2
  128. package/dist/typescript/src/lib/Components/BaseInput/types.d.ts.map +1 -1
  129. package/dist/typescript/src/lib/Components/SegmentedControl/SegmentedControl.d.ts +1 -1
  130. package/dist/typescript/src/lib/Components/SegmentedControl/SegmentedControl.d.ts.map +1 -1
  131. package/dist/typescript/src/lib/Components/SegmentedControl/types.d.ts +4 -0
  132. package/dist/typescript/src/lib/Components/SegmentedControl/types.d.ts.map +1 -1
  133. package/dist/typescript/src/lib/Components/TextInput/TextInput.d.ts +4 -3
  134. package/dist/typescript/src/lib/Components/TextInput/TextInput.d.ts.map +1 -1
  135. package/dist/typescript/src/lib/Components/Trend/Trend.d.ts +3 -0
  136. package/dist/typescript/src/lib/Components/Trend/Trend.d.ts.map +1 -0
  137. package/dist/typescript/src/lib/Components/Trend/index.d.ts +3 -0
  138. package/dist/typescript/src/lib/Components/Trend/index.d.ts.map +1 -0
  139. package/dist/typescript/src/lib/Components/Trend/types.d.ts +20 -0
  140. package/dist/typescript/src/lib/Components/Trend/types.d.ts.map +1 -0
  141. package/dist/typescript/src/lib/Components/index.d.ts +5 -4
  142. package/dist/typescript/src/lib/Components/index.d.ts.map +1 -1
  143. package/dist/typescript/src/lib/Symbols/Icons/ClockFill.d.ts.map +1 -1
  144. package/package.json +2 -2
  145. package/src/i18n/i18n.ts +12 -1
  146. package/src/i18n/locales/en.json +5 -0
  147. package/src/lib/Components/AddressInput/AddressInput.mdx +10 -12
  148. package/src/lib/Components/AddressInput/AddressInput.stories.tsx +2 -1
  149. package/src/lib/Components/AmountInput/AmountInput.mdx +3 -3
  150. package/src/lib/Components/BaseInput/BaseInput.tsx +61 -29
  151. package/src/lib/Components/BaseInput/types.ts +10 -2
  152. package/src/lib/Components/DotCount/DotCount.stories.tsx +12 -10
  153. package/src/lib/Components/DotIndicator/DotIndicator.stories.tsx +1 -3
  154. package/src/lib/Components/MediaImage/MediaImage.test.tsx +7 -3
  155. package/src/lib/Components/SearchInput/SearchInput.mdx +4 -10
  156. package/src/lib/Components/SearchInput/SearchInput.stories.tsx +2 -1
  157. package/src/lib/Components/SegmentedControl/SegmentedControl.mdx +34 -4
  158. package/src/lib/Components/SegmentedControl/SegmentedControl.stories.tsx +34 -6
  159. package/src/lib/Components/SegmentedControl/SegmentedControl.test.tsx +27 -0
  160. package/src/lib/Components/SegmentedControl/SegmentedControl.tsx +2 -0
  161. package/src/lib/Components/SegmentedControl/types.ts +4 -0
  162. package/src/lib/Components/TextInput/TextInput.mdx +8 -10
  163. package/src/lib/Components/TextInput/TextInput.stories.tsx +41 -1
  164. package/src/lib/Components/TextInput/TextInput.test.tsx +90 -0
  165. package/src/lib/Components/TextInput/TextInput.tsx +4 -3
  166. package/src/lib/Components/Trend/Trend.mdx +106 -0
  167. package/src/lib/Components/Trend/Trend.stories.tsx +61 -0
  168. package/src/lib/Components/Trend/Trend.test.tsx +125 -0
  169. package/src/lib/Components/Trend/Trend.tsx +118 -0
  170. package/src/lib/Components/Trend/index.ts +2 -0
  171. package/src/lib/Components/Trend/types.ts +20 -0
  172. package/src/lib/Components/index.ts +5 -4
  173. package/src/lib/Symbols/Icons/Ar.tsx +2 -2
  174. package/src/lib/Symbols/Icons/ArrowLeft.tsx +1 -1
  175. package/src/lib/Symbols/Icons/ArrowRight.tsx +1 -1
  176. package/src/lib/Symbols/Icons/BasketPutIn.tsx +1 -1
  177. package/src/lib/Symbols/Icons/Clip.tsx +1 -1
  178. package/src/lib/Symbols/Icons/ClockFill.tsx +1 -9
  179. package/src/lib/Symbols/Icons/CloudDownload.tsx +1 -1
  180. package/src/lib/Symbols/Icons/CloudUpload.tsx +1 -1
  181. package/src/lib/Symbols/Icons/CoinsAddPlus.tsx +1 -1
  182. package/src/lib/Symbols/Icons/CoinsCheck.tsx +1 -1
  183. package/src/lib/Symbols/Icons/CoinsCross.tsx +1 -1
  184. package/src/lib/Symbols/Icons/ColorPalette.tsx +1 -1
  185. package/src/lib/Symbols/Icons/CryptoBitcoinCoin.tsx +1 -1
  186. package/src/lib/Symbols/Icons/Csv.tsx +1 -1
  187. package/src/lib/Symbols/Icons/Discord.tsx +1 -1
  188. package/src/lib/Symbols/Icons/Download.tsx +1 -1
  189. package/src/lib/Symbols/Icons/Exchange.tsx +1 -1
  190. package/src/lib/Symbols/Icons/ExchangeFill.tsx +1 -1
  191. package/src/lib/Symbols/Icons/ExitLogout.tsx +1 -1
  192. package/src/lib/Symbols/Icons/Expand.tsx +1 -1
  193. package/src/lib/Symbols/Icons/Experiment2.tsx +1 -1
  194. package/src/lib/Symbols/Icons/ExternalLink.tsx +1 -1
  195. package/src/lib/Symbols/Icons/FileDownload.tsx +1 -1
  196. package/src/lib/Symbols/Icons/Github.tsx +1 -1
  197. package/src/lib/Symbols/Icons/HandCard.tsx +1 -1
  198. package/src/lib/Symbols/Icons/Infinite.tsx +1 -1
  199. package/src/lib/Symbols/Icons/MobileArrow.tsx +1 -1
  200. package/src/lib/Symbols/Icons/NftHide.tsx +1 -1
  201. package/src/lib/Symbols/Icons/Parachute.tsx +1 -1
  202. package/src/lib/Symbols/Icons/PictureImage.tsx +1 -1
  203. package/src/lib/Symbols/Icons/Range.tsx +1 -1
  204. package/src/lib/Symbols/Icons/Reddit.tsx +2 -2
  205. package/src/lib/Symbols/Icons/Reduce.tsx +1 -1
  206. package/src/lib/Symbols/Icons/Screens.tsx +1 -1
  207. package/src/lib/Symbols/Icons/Shapes.tsx +1 -1
  208. package/src/lib/Symbols/Icons/Share.tsx +1 -1
  209. package/src/lib/Symbols/Icons/StarHalf.tsx +1 -1
  210. package/src/lib/Symbols/Icons/TransferHorizontal.tsx +1 -1
  211. package/src/lib/Symbols/Icons/TransferVertical.tsx +1 -1
  212. package/src/lib/Symbols/Icons/Truck.tsx +1 -1
  213. package/src/lib/Symbols/Icons/Usb.tsx +1 -1
  214. package/src/lib/Symbols/Icons/UserArrowRight.tsx +1 -1
  215. package/src/lib/Symbols/Icons/WalletInput.tsx +1 -1
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  DisabledProvider,
3
+ resolveBaseInputPlaceholder,
3
4
  useDisabledContext,
4
5
  } from '@ledgerhq/lumen-utils-shared';
5
6
  import {
@@ -21,6 +22,7 @@ import { useCommonTranslation } from '../../../i18n';
21
22
  import type { LumenStyleSheetTheme } from '../../../styles';
22
23
  import { useStyleSheet, useTheme } from '../../../styles';
23
24
  import { useTimingConfig } from '../../Animations/useTimingConfig';
25
+ import { CheckmarkCircleFill } from '../../Symbols/Icons/CheckmarkCircleFill';
24
26
  import { DeleteCircleFill } from '../../Symbols/Icons/DeleteCircleFill';
25
27
  import { RuntimeConstants } from '../../utils';
26
28
  import { InteractiveIcon } from '../InteractiveIcon';
@@ -34,7 +36,8 @@ export const BaseInput = ({
34
36
  inputStyle,
35
37
  labelStyle,
36
38
  label,
37
- errorMessage,
39
+ helperText,
40
+ status,
38
41
  hideClearButton,
39
42
  onChangeText: onChangeTextProp,
40
43
  editable,
@@ -42,6 +45,7 @@ export const BaseInput = ({
42
45
  prefix,
43
46
  suffix,
44
47
  ref,
48
+ placeholder: placeholderProp,
45
49
  ...props
46
50
  }: BaseInputProps) => {
47
51
  const disabled = useDisabledContext({
@@ -65,6 +69,12 @@ export const BaseInput = ({
65
69
  ? !!props.value && props.value.length > 0
66
70
  : uncontrolledValue.length > 0;
67
71
 
72
+ const { inputPlaceholder, labelStaysFloatedWithPlaceholder } =
73
+ resolveBaseInputPlaceholder({
74
+ label,
75
+ placeholder: placeholderProp,
76
+ });
77
+
68
78
  const showClearButton = hasContent && !disabled && !hideClearButton;
69
79
 
70
80
  const handleChangeText = useCallback(
@@ -87,7 +97,7 @@ export const BaseInput = ({
87
97
  };
88
98
 
89
99
  const styles = useStyles({
90
- hasError: !!errorMessage,
100
+ status,
91
101
  isFocused,
92
102
  isEditable: !disabled,
93
103
  hasLabel: !!label,
@@ -97,8 +107,9 @@ export const BaseInput = ({
97
107
  hasContent,
98
108
  isFocused,
99
109
  showClearButton,
100
- hasError: !!errorMessage,
110
+ status,
101
111
  isEditable: !disabled,
112
+ labelStaysFloatedWithPlaceholder,
102
113
  });
103
114
 
104
115
  return (
@@ -114,6 +125,7 @@ export const BaseInput = ({
114
125
  <TextInput
115
126
  ref={inputRef}
116
127
  value={value}
128
+ placeholder={inputPlaceholder}
117
129
  style={StyleSheet.flatten([styles.input, inputStyle])}
118
130
  onFocus={() => setIsFocused(true)}
119
131
  onBlur={() => setIsFocused(false)}
@@ -158,10 +170,13 @@ export const BaseInput = ({
158
170
  )}
159
171
  </Pressable>
160
172
 
161
- {errorMessage && (
162
- <View style={styles.errorContainer}>
163
- <DeleteCircleFill size={16} color='error' />
164
- <Text style={styles.errorText}>{errorMessage}</Text>
173
+ {!!helperText && (
174
+ <View style={styles.helperContainer}>
175
+ {status === 'error' && <DeleteCircleFill size={16} color='error' />}
176
+ {status === 'success' && (
177
+ <CheckmarkCircleFill size={16} color='success' />
178
+ )}
179
+ <Text style={styles.helperText}>{helperText}</Text>
165
180
  </View>
166
181
  )}
167
182
  </Box>
@@ -170,18 +185,25 @@ export const BaseInput = ({
170
185
  };
171
186
 
172
187
  const useStyles = ({
173
- hasError,
188
+ status,
174
189
  isFocused,
175
190
  isEditable,
176
191
  hasLabel,
177
192
  }: {
178
- hasError: boolean;
193
+ status: 'error' | 'success' | undefined;
179
194
  isFocused: boolean;
180
195
  isEditable: boolean;
181
196
  hasLabel: boolean;
182
197
  }) => {
183
198
  return useStyleSheet(
184
199
  (t) => {
200
+ const hasStatusBorder = status === 'error' || status === 'success';
201
+ const statusBorderColors = {
202
+ error: t.colors.border.error,
203
+ success: t.colors.border.success,
204
+ } as const;
205
+ const statusBorderColor = status ? statusBorderColors[status] : undefined;
206
+
185
207
  return {
186
208
  container: StyleSheet.flatten([
187
209
  {
@@ -198,15 +220,16 @@ const useStyles = ({
198
220
  borderColor: 'transparent',
199
221
  overflow: 'hidden',
200
222
  },
201
- hasError && {
202
- borderWidth: 1,
203
- borderColor: t.colors.border.error,
204
- },
223
+ hasStatusBorder &&
224
+ statusBorderColor && {
225
+ borderWidth: isFocused ? t.borderWidth.s2 : t.borderWidth.s1,
226
+ borderColor: statusBorderColor,
227
+ },
205
228
  !isEditable && {
206
229
  backgroundColor: t.colors.bg.disabled,
207
230
  },
208
231
  isFocused &&
209
- !hasError &&
232
+ !hasStatusBorder &&
210
233
  isEditable && { borderColor: t.colors.border.active },
211
234
  ]),
212
235
  input: StyleSheet.flatten([
@@ -217,31 +240,37 @@ const useStyles = ({
217
240
  color: t.colors.text.base,
218
241
  backgroundColor: t.colors.bg.muted,
219
242
  outline: 'none',
220
- ...t.typographies.body2,
221
- paddingTop: t.spacings.s12,
222
- paddingBottom: t.spacings.s12,
243
+ ...t.typographies.body1,
244
+ paddingTop: t.spacings.s4,
245
+ paddingBottom: t.spacings.s2,
223
246
  },
224
247
  hasLabel && {
225
248
  paddingTop: t.spacings.s20,
226
249
  paddingBottom: t.spacings.s4,
227
250
  paddingHorizontal: 0,
251
+ ...t.typographies.body2,
228
252
  },
229
- RuntimeConstants.isIOS && hasLabel && { lineHeight: 0 },
253
+ RuntimeConstants.isIOS && { lineHeight: 0 },
230
254
  RuntimeConstants.isAndroid && { includeFontPadding: false },
231
255
  !isEditable && {
232
256
  backgroundColor: t.colors.bg.disabled,
233
257
  color: t.colors.text.disabled,
234
258
  },
235
259
  ]),
236
- errorContainer: {
260
+ helperContainer: {
237
261
  marginTop: t.spacings.s8,
238
262
  flexDirection: 'row',
239
263
  alignItems: 'center',
240
264
  gap: t.spacings.s2,
241
265
  },
242
- errorText: {
243
- color: t.colors.text.error,
266
+ helperText: {
244
267
  ...t.typographies.body3,
268
+ flex: 1,
269
+ color: {
270
+ error: t.colors.text.error,
271
+ success: t.colors.text.success,
272
+ default: t.colors.text.muted,
273
+ }[status ?? 'default'],
245
274
  },
246
275
  suffixContainer: {
247
276
  minWidth: t.sizes.s20,
@@ -250,7 +279,7 @@ const useStyles = ({
250
279
  },
251
280
  };
252
281
  },
253
- [hasError, isFocused, isEditable, hasLabel],
282
+ [status, isFocused, isEditable, hasLabel],
254
283
  );
255
284
  };
256
285
 
@@ -296,14 +325,16 @@ const useFloatingLabelStyles = ({
296
325
  isFocused,
297
326
  hasContent,
298
327
  showClearButton,
299
- hasError,
328
+ status,
300
329
  isEditable,
330
+ labelStaysFloatedWithPlaceholder,
301
331
  }: {
302
332
  isFocused: boolean;
303
333
  hasContent: boolean;
304
334
  showClearButton: boolean;
305
- hasError: boolean;
335
+ status: 'error' | 'success' | undefined;
306
336
  isEditable: boolean;
337
+ labelStaysFloatedWithPlaceholder: boolean;
307
338
  }) => {
308
339
  const { theme } = useTheme();
309
340
 
@@ -320,20 +351,21 @@ const useFloatingLabelStyles = ({
320
351
  showClearButton && {
321
352
  width: '92%',
322
353
  },
354
+ status === 'error' && {
355
+ color: t.colors.text.error,
356
+ },
323
357
  !isEditable && {
324
358
  color: t.colors.text.disabled,
325
359
  },
326
- hasError && {
327
- color: t.colors.text.error,
328
- },
329
360
  ]),
330
361
  }),
331
- [hasContent, showClearButton, hasError, isEditable],
362
+ [hasContent, showClearButton, status, isEditable],
332
363
  );
333
364
 
334
365
  const { animatedStyle } = useAnimatedFloatingLabel({
335
366
  theme,
336
- isFloatingLabel: isFocused || hasContent,
367
+ isFloatingLabel:
368
+ isFocused || hasContent || labelStaysFloatedWithPlaceholder,
337
369
  });
338
370
 
339
371
  return { label, animatedStyle };
@@ -6,6 +6,8 @@ import type {
6
6
  } from 'react-native';
7
7
  import type { BoxProps } from '../Utility';
8
8
 
9
+ export type BaseInputStatus = 'error' | 'success';
10
+
9
11
  export type BaseInputProps = {
10
12
  /**
11
13
  * The label text that floats above the input when focused or filled.
@@ -35,9 +37,15 @@ export type BaseInputProps = {
35
37
  */
36
38
  labelStyle?: StyleProp<TextStyle>;
37
39
  /**
38
- * An optional error message displayed below the input.
40
+ * Optional text shown below the input (hint, error, or success copy).
41
+ * Pair with `status` for error/success styling and icons; omit `status` for a neutral hint.
42
+ */
43
+ helperText?: string;
44
+ /**
45
+ * Visual state for border, helper text, helper icon, and the label in error state.
46
+ * Omit when `helperText` is a neutral hint.
39
47
  */
40
- errorMessage?: string;
48
+ status?: BaseInputStatus;
41
49
  /**
42
50
  * Custom content to render after the input (right side in LTR).
43
51
  * @example suffix={<Icon />}
@@ -1,6 +1,5 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-native-web-vite';
2
2
  import { useState } from 'react';
3
- import { Avatar } from '../Avatar/Avatar';
4
3
  import { MediaImage } from '../MediaImage/MediaImage';
5
4
  import {
6
5
  SegmentedControl,
@@ -89,7 +88,14 @@ export const WithChildren: Story = {
89
88
 
90
89
  return (
91
90
  <Box lx={{ gap: 's24' }}>
92
- <Box lx={{ flexDirection: 'row', alignItems: 'center', gap: 's12' }}>
91
+ <Box
92
+ lx={{
93
+ flexDirection: 'row',
94
+ alignItems: 'center',
95
+ justifyContent: 'center',
96
+ gap: 's12',
97
+ }}
98
+ >
93
99
  <DotCount value={5} size='md'>
94
100
  <MediaImage
95
101
  src='https://crypto-icons.ledger.com/BTC.png'
@@ -98,12 +104,6 @@ export const WithChildren: Story = {
98
104
  shape='circle'
99
105
  />
100
106
  </DotCount>
101
- <DotCount value={100} size='md'>
102
- <Avatar
103
- src='https://plus.unsplash.com/premium_photo-1689551670902-19b441a6afde?q=80&w=774&auto=format&fit=crop'
104
- size='md'
105
- />
106
- </DotCount>
107
107
  </Box>
108
108
  <SegmentedControl
109
109
  selectedValue={fitState}
@@ -111,9 +111,11 @@ export const WithChildren: Story = {
111
111
  tabLayout='fit'
112
112
  accessibilityLabel='Fit layout'
113
113
  >
114
- <SegmentedControlButton value='preview'>
114
+ <SegmentedControlButton
115
+ value='preview'
116
+ trailingContent={<DotCount value={3} size='md' />}
117
+ >
115
118
  Preview
116
- <DotCount value={3} size='md' style={{ marginLeft: 6 }} />
117
119
  </SegmentedControlButton>
118
120
  <SegmentedControlButton value='raw'>Raw</SegmentedControlButton>
119
121
  <SegmentedControlButton value='blame'>Blame</SegmentedControlButton>
@@ -66,9 +66,7 @@ export const WithChildren: Story = {
66
66
  <DotIndicator appearance='red'>
67
67
  <Button size='sm'>Submit</Button>
68
68
  </DotIndicator>
69
- <DotIndicator appearance='red'>
70
- <Avatar size='md' />
71
- </DotIndicator>
69
+ <Avatar size='md' showNotification />
72
70
  <DotIndicator appearance='red'>
73
71
  <IconButton accessibilityLabel='Settings' icon={Settings} />
74
72
  </DotIndicator>
@@ -1,6 +1,6 @@
1
1
  import { describe, it, expect } from '@jest/globals';
2
2
  import { ledgerLiveThemes } from '@ledgerhq/lumen-design-core';
3
- import { render, waitFor } from '@testing-library/react-native';
3
+ import { act, render, waitFor } from '@testing-library/react-native';
4
4
  import { ThemeProvider } from '../ThemeProvider/ThemeProvider';
5
5
  import { MediaImage } from './MediaImage';
6
6
 
@@ -89,7 +89,9 @@ describe('MediaImage Component', () => {
89
89
  );
90
90
 
91
91
  const img = getByTestId('media-image-img');
92
- img.props.onError();
92
+ act(() => {
93
+ img.props.onError();
94
+ });
93
95
 
94
96
  rerender(
95
97
  <TestWrapper>
@@ -140,7 +142,9 @@ describe('MediaImage Component', () => {
140
142
  );
141
143
 
142
144
  const img = getByTestId('media-image-img');
143
- img.props.onError();
145
+ act(() => {
146
+ img.props.onError();
147
+ });
144
148
 
145
149
  rerender(
146
150
  <TestWrapper>
@@ -67,17 +67,10 @@ Alternatively, use `editable={false}` to prevent editing without applying the mu
67
67
 
68
68
  ### Error State
69
69
 
70
- The search component supports error handling through `errorMessage` which displays an error message below the input with error styling including a red border and text color.
70
+ The search component supports error handling through `helperText` and `status` (`'error'` \| `'success'`), which show copy below the input with matching border and text styling.
71
71
 
72
72
  <Canvas of={SearchInputStories.WithError} />
73
73
 
74
- The error message will be automatically:
75
-
76
- - Connected to the input
77
- - Displayed with a warning icon
78
- - Styled in the error color
79
- - Announced by screen readers
80
-
81
74
  ## Controlled vs Uncontrolled
82
75
 
83
76
  The SearchInput component supports both controlled and uncontrolled usage.
@@ -232,13 +225,14 @@ function MyComponent() {
232
225
  placeholder='Search products'
233
226
  value={query}
234
227
  onChangeText={handleSearch}
235
- errorMessage={error}
228
+ helperText={error || undefined}
229
+ status={error ? 'error' : undefined}
236
230
  />
237
231
  );
238
232
  }
239
233
  ```
240
234
 
241
- > **Note:** Error messages are optional. Use `errorMessage` to display an error message below the search input.
235
+ > **Note:** Helper text is optional. Use `helperText` with `status="error"` to show validation feedback below the search input.
242
236
 
243
237
  ### With Custom Styling
244
238
 
@@ -75,7 +75,8 @@ export const WithError: Story = {
75
75
  ),
76
76
  args: {
77
77
  placeholder: 'Search products',
78
- errorMessage: 'Search term is invalid',
78
+ helperText: 'Search term is invalid',
79
+ status: 'error',
79
80
  editable: true,
80
81
  hideClearButton: false,
81
82
  },
@@ -25,6 +25,7 @@ SegmentedControl is a tab bar–style component for switching between mutually e
25
25
  - **Segments**: Individual options the user can select.
26
26
  - **Selected state**: The active segment (sliding pill + semi-bold label).
27
27
  - **Optional icon**: Icon to the left of the label (from Symbols).
28
+ - **Optional trailing content**: Any node (e.g. `DotCount`) rendered to the right of the label.
28
29
  - **Appearance**: Use `appearance="background"` (default) for a surface background, or `appearance="no-background"` for a transparent container.
29
30
 
30
31
  ## Properties
@@ -40,6 +41,12 @@ You can use segments with text only (Base), or add an icon to each button (WithI
40
41
 
41
42
  <Canvas of={SegmentedControlStories.WithIcons} />
42
43
 
44
+ ### With trailing content
45
+
46
+ Use `trailingContent` to render content (e.g. `DotCount`) to the right of the label.
47
+
48
+ <Canvas of={SegmentedControlStories.WithTrailingContent} />
49
+
43
50
  ## Responsive Layout
44
51
 
45
52
  SegmentedControl lays out segments in a horizontal row with equal width per segment. The sliding pill animates to the selected segment.
@@ -80,6 +87,33 @@ export default function Example() {
80
87
  }
81
88
  ```
82
89
 
90
+ ## With trailing content
91
+
92
+ Pass any node via `trailingContent` to render it to the right of the label. Use it for badges like `DotCount` to surface counts per segment.
93
+
94
+ ```tsx
95
+ import { SegmentedControl, SegmentedControlButton, DotCount } from '@ledgerhq/lumen-ui-rnative';
96
+
97
+ function Example() {
98
+ const [state, setState] = React.useState('tokens');
99
+ return (
100
+ <SegmentedControl
101
+ selectedValue={state}
102
+ onSelectedChange={setState}
103
+ accessibilityLabel='Asset section'
104
+ >
105
+ <SegmentedControlButton value='tokens' trailingContent={<DotCount value={3} />}>
106
+ Tokens
107
+ </SegmentedControlButton>
108
+ <SegmentedControlButton value='nfts' trailingContent={<DotCount value={12} />}>
109
+ NFTs
110
+ </SegmentedControlButton>
111
+ <SegmentedControlButton value='activity'>Activity</SegmentedControlButton>
112
+ </SegmentedControl>
113
+ );
114
+ }
115
+ ```
116
+
83
117
  ## With icons
84
118
 
85
119
  Pass an icon from symbols to each button for a left-positioned icon. Use icons on all segments or none for consistency.
@@ -113,9 +147,5 @@ function Example() {
113
147
  }
114
148
  ```
115
149
 
116
- <Box lx={{ flexDirection: 'column', gap: 's24' }}>
117
- <CommonRulesDoAndDont />
118
- </Box>
119
-
120
150
  </Tab>
121
151
  </CustomTabs>
@@ -1,6 +1,7 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-native-web-vite';
2
2
  import { useState } from 'react';
3
- import { Coins, Nft, TransferHorizontal, Settings } from '../../Symbols';
3
+ import { Coins, Nft, TransferHorizontal } from '../../Symbols';
4
+ import { DotCount } from '../DotCount';
4
5
  import { Box } from '../Utility';
5
6
  import { SegmentedControl, SegmentedControlButton } from './SegmentedControl';
6
7
 
@@ -74,11 +75,8 @@ export const WithIcons: Story = {
74
75
  <SegmentedControlButton value='nfts' icon={Nft}>
75
76
  NFTs
76
77
  </SegmentedControlButton>
77
- <SegmentedControlButton value='activity' icon={TransferHorizontal}>
78
- Activity
79
- </SegmentedControlButton>
80
- <SegmentedControlButton value='settings' icon={Settings}>
81
- Settings
78
+ <SegmentedControlButton value='trade' icon={TransferHorizontal}>
79
+ Trade
82
80
  </SegmentedControlButton>
83
81
  </SegmentedControl>
84
82
  );
@@ -142,3 +140,33 @@ export const Disabled: Story = {
142
140
  </SegmentedControl>
143
141
  ),
144
142
  };
143
+
144
+ export const WithTrailingContent: Story = {
145
+ args: {} as React.ComponentProps<typeof SegmentedControl>,
146
+ render: (args) => {
147
+ const [state, setState] = useState('tokens');
148
+
149
+ return (
150
+ <SegmentedControl
151
+ {...args}
152
+ selectedValue={state}
153
+ onSelectedChange={setState}
154
+ accessibilityLabel='Asset section'
155
+ >
156
+ <SegmentedControlButton
157
+ value='tokens'
158
+ trailingContent={<DotCount value={3} />}
159
+ >
160
+ Tokens
161
+ </SegmentedControlButton>
162
+ <SegmentedControlButton
163
+ value='nfts'
164
+ trailingContent={<DotCount value={12} />}
165
+ >
166
+ NFTs
167
+ </SegmentedControlButton>
168
+ <SegmentedControlButton value='trade'>Trade</SegmentedControlButton>
169
+ </SegmentedControl>
170
+ );
171
+ },
172
+ };
@@ -1,6 +1,7 @@
1
1
  import { describe, it, expect, jest } from '@jest/globals';
2
2
  import { ledgerLiveThemes } from '@ledgerhq/lumen-design-core';
3
3
  import { render, fireEvent } from '@testing-library/react-native';
4
+ import { DotCount } from '../DotCount';
4
5
  import { ThemeProvider } from '../ThemeProvider/ThemeProvider';
5
6
  import { SegmentedControl, SegmentedControlButton } from './SegmentedControl';
6
7
 
@@ -53,4 +54,30 @@ describe('SegmentedControl', () => {
53
54
 
54
55
  expect(onSelectedChange).toHaveBeenCalledWith('receive');
55
56
  });
57
+
58
+ it('renders trailingContent inside segment buttons', () => {
59
+ const { getByLabelText } = render(
60
+ <TestWrapper>
61
+ <SegmentedControl
62
+ selectedValue='tokens'
63
+ onSelectedChange={() => {
64
+ /* empty */
65
+ }}
66
+ accessibilityLabel='Asset section'
67
+ >
68
+ <SegmentedControlButton
69
+ value='tokens'
70
+ trailingContent={
71
+ <DotCount value={3} accessibilityLabel='3 tokens' />
72
+ }
73
+ >
74
+ Tokens
75
+ </SegmentedControlButton>
76
+ <SegmentedControlButton value='nfts'>NFTs</SegmentedControlButton>
77
+ </SegmentedControl>
78
+ </TestWrapper>,
79
+ );
80
+
81
+ expect(getByLabelText('3 tokens')).toBeTruthy();
82
+ });
56
83
  });
@@ -21,6 +21,7 @@ export function SegmentedControlButton({
21
21
  value,
22
22
  children,
23
23
  icon: Icon,
24
+ trailingContent,
24
25
  onPress,
25
26
  ...props
26
27
  }: SegmentedControlButtonProps) {
@@ -71,6 +72,7 @@ export function SegmentedControlButton({
71
72
  >
72
73
  {children}
73
74
  </Text>
75
+ {trailingContent}
74
76
  </Box>
75
77
  </Pressable>
76
78
  );
@@ -56,6 +56,10 @@ export type SegmentedControlButtonProps = {
56
56
  * Optional icon shown to the left of the label (from Symbols).
57
57
  */
58
58
  icon?: IconComponent;
59
+ /**
60
+ * Optional content shown to the right of the label (e.g. DotCount badge).
61
+ */
62
+ trailingContent?: ReactNode;
59
63
  /**
60
64
  * Optional callback when the button is pressed (in addition to onSelectedChange on the parent).
61
65
  */
@@ -34,6 +34,10 @@ The label text automatically floats above the input when content is entered, pro
34
34
 
35
35
  <Canvas of={TextInputStories.WithContent} />
36
36
 
37
+ ### Label and placeholder
38
+
39
+ <Canvas of={TextInputStories.WithLabelAndPlaceholder} />
40
+
37
41
  ### Clear Button
38
42
 
39
43
  A clear button (×) appears **automatically** when input has content.
@@ -54,17 +58,10 @@ Use `onClear` to extend the default clear behavior with custom logic.
54
58
 
55
59
  ### Error State
56
60
 
57
- The input supports error handling through `errorMessage` which displays an error message below the input with error styling including a red border and text color.
61
+ The input supports error handling through `helperText` and `status` (`'error'` \| `'success'`), which show copy below the input with matching border and text styling.
58
62
 
59
63
  <Canvas of={TextInputStories.WithError} />
60
64
 
61
- The error message will be automatically:
62
-
63
- - Connected to the input
64
- - Displayed with a warning icon
65
- - Styled in the error color
66
- - Announced by screen readers
67
-
68
65
  ### Disabled State
69
66
 
70
67
  The input can be fully disabled using the `disabled` prop, which prevents interaction and applies a muted visual style.
@@ -221,13 +218,14 @@ function MyComponent() {
221
218
  label='Username'
222
219
  value={value}
223
220
  onChangeText={handleChange}
224
- errorMessage={error}
221
+ helperText={error || undefined}
222
+ status={error ? 'error' : undefined}
225
223
  />
226
224
  );
227
225
  }
228
226
  ```
229
227
 
230
- > **Note:** Error messages are optional. Use `errorMessage` to display an error message below the input.
228
+ > **Note:** Helper text is optional. Use `helperText` with `status="error"` to show validation feedback below the input.
231
229
 
232
230
  ### With Custom Styling
233
231