jfs-components 0.0.62 → 0.0.64

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 (255) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/lib/commonjs/components/Accordion/Accordion.js +1 -1
  3. package/lib/commonjs/components/ActionFooter/ActionFooter.js +1 -1
  4. package/lib/commonjs/components/ActionTile/ActionTile.js +2 -1
  5. package/lib/commonjs/components/AmountInput/AmountInput.js +2 -1
  6. package/lib/commonjs/components/AppBar/AppBar.js +1 -1
  7. package/lib/commonjs/components/Avatar/Avatar.js +184 -162
  8. package/lib/commonjs/components/AvatarGroup/AvatarGroup.js +1 -1
  9. package/lib/commonjs/components/Badge/Badge.js +2 -1
  10. package/lib/commonjs/components/Balance/Balance.js +2 -1
  11. package/lib/commonjs/components/BottomNav/BottomNav.js +2 -1
  12. package/lib/commonjs/components/BottomNavItem/BottomNavItem.js +106 -86
  13. package/lib/commonjs/components/Button/Button.js +190 -93
  14. package/lib/commonjs/components/ButtonGroup/ButtonGroup.js +1 -1
  15. package/lib/commonjs/components/Card/Card.js +2 -1
  16. package/lib/commonjs/components/CardCTA/CardCTA.js +1 -1
  17. package/lib/commonjs/components/CardProviderInfo/CardProviderInfo.js +1 -1
  18. package/lib/commonjs/components/Carousel/Carousel.js +3 -2
  19. package/lib/commonjs/components/Checkbox/Checkbox.js +2 -1
  20. package/lib/commonjs/components/ChipGroup/ChipGroup.js +1 -1
  21. package/lib/commonjs/components/ChipSelect/ChipSelect.js +2 -1
  22. package/lib/commonjs/components/DebitCard/DebitCard.js +1 -1
  23. package/lib/commonjs/components/Disclaimer/Disclaimer.js +2 -1
  24. package/lib/commonjs/components/Divider/Divider.js +2 -1
  25. package/lib/commonjs/components/Drawer/Drawer.js +109 -48
  26. package/lib/commonjs/components/EmptyState/EmptyState.js +2 -1
  27. package/lib/commonjs/components/FilterBar/FilterBar.js +1 -1
  28. package/lib/commonjs/components/Form/Form.js +2 -1
  29. package/lib/commonjs/components/FormField/FormField.js +3 -2
  30. package/lib/commonjs/components/HStack/HStack.js +1 -1
  31. package/lib/commonjs/components/HoldingsCard/HoldingsCard.js +2 -1
  32. package/lib/commonjs/components/IconButton/IconButton.js +118 -128
  33. package/lib/commonjs/components/IconCapsule/IconCapsule.js +61 -57
  34. package/lib/commonjs/components/InputSearch/InputSearch.js +7 -3
  35. package/lib/commonjs/components/LazyList/LazyList.js +1 -1
  36. package/lib/commonjs/components/LinearMeter/LinearMeter.js +3 -2
  37. package/lib/commonjs/components/ListGroup/ListGroup.js +1 -1
  38. package/lib/commonjs/components/ListItem/ListItem.js +190 -142
  39. package/lib/commonjs/components/MediaCard/MediaCard.js +3 -3
  40. package/lib/commonjs/components/MerchantProfile/MerchantProfile.js +2 -1
  41. package/lib/commonjs/components/MoneyValue/MoneyValue.js +2 -1
  42. package/lib/commonjs/components/NavArrow/NavArrow.js +82 -59
  43. package/lib/commonjs/components/NoteInput/NoteInput.js +2 -1
  44. package/lib/commonjs/components/Nudge/Nudge.js +1 -1
  45. package/lib/commonjs/components/Numpad/Numpad.js +2 -1
  46. package/lib/commonjs/components/OTP/OTP.js +1 -1
  47. package/lib/commonjs/components/PaymentFeedback/PaymentFeedback.js +2 -1
  48. package/lib/commonjs/components/Popup/Popup.js +2 -1
  49. package/lib/commonjs/components/ProductLabel/ProductLabel.js +2 -1
  50. package/lib/commonjs/components/ProgressBadge/ProgressBadge.js +2 -1
  51. package/lib/commonjs/components/RadioButton/RadioButton.js +2 -1
  52. package/lib/commonjs/components/RechargeCard/RechargeCard.js +2 -1
  53. package/lib/commonjs/components/Screen/Screen.js +1 -1
  54. package/lib/commonjs/components/Section/Section.js +500 -166
  55. package/lib/commonjs/components/SegmentedControl/SegmentedControl.js +3 -2
  56. package/lib/commonjs/components/StatItem/StatItem.js +2 -1
  57. package/lib/commonjs/components/StatusHero/StatusHero.js +2 -1
  58. package/lib/commonjs/components/Stepper/Step.js +2 -1
  59. package/lib/commonjs/components/Stepper/StepLabel.js +2 -1
  60. package/lib/commonjs/components/Stepper/Stepper.js +2 -1
  61. package/lib/commonjs/components/SupportText/SupportText.js +2 -1
  62. package/lib/commonjs/components/SupportText/SupportTextIcon.js +2 -1
  63. package/lib/commonjs/components/SwappableAmount/SwappableAmount.js +2 -1
  64. package/lib/commonjs/components/Tabs/TabItem.js +2 -1
  65. package/lib/commonjs/components/Tabs/Tabs.js +2 -1
  66. package/lib/commonjs/components/Text/Text.js +2 -1
  67. package/lib/commonjs/components/TextInput/TextInput.js +2 -2
  68. package/lib/commonjs/components/ThreadHero/ThreadHero.js +2 -1
  69. package/lib/commonjs/components/Title/Title.js +2 -1
  70. package/lib/commonjs/components/Toast/Toast.js +2 -1
  71. package/lib/commonjs/components/Toggle/Toggle.js +2 -1
  72. package/lib/commonjs/components/Tooltip/Tooltip.js +2 -1
  73. package/lib/commonjs/components/TransactionBubble/TransactionBubble.js +1 -1
  74. package/lib/commonjs/components/TransactionDetails/TransactionDetails.js +2 -2
  75. package/lib/commonjs/components/TransactionStatus/TransactionStatus.js +3 -2
  76. package/lib/commonjs/components/UpiHandle/UpiHandle.js +144 -110
  77. package/lib/commonjs/components/VStack/VStack.js +1 -1
  78. package/lib/commonjs/design-tokens/figma-variables-resolver.js +21 -3
  79. package/lib/commonjs/icons/registry.js +1 -1
  80. package/lib/commonjs/utils/react-utils.js +17 -0
  81. package/lib/module/components/Accordion/Accordion.js +2 -2
  82. package/lib/module/components/ActionFooter/ActionFooter.js +2 -2
  83. package/lib/module/components/ActionTile/ActionTile.js +2 -1
  84. package/lib/module/components/AmountInput/AmountInput.js +2 -1
  85. package/lib/module/components/AppBar/AppBar.js +2 -2
  86. package/lib/module/components/Avatar/Avatar.js +184 -162
  87. package/lib/module/components/AvatarGroup/AvatarGroup.js +2 -2
  88. package/lib/module/components/Badge/Badge.js +2 -1
  89. package/lib/module/components/Balance/Balance.js +2 -1
  90. package/lib/module/components/BottomNav/BottomNav.js +2 -1
  91. package/lib/module/components/BottomNavItem/BottomNavItem.js +108 -88
  92. package/lib/module/components/Button/Button.js +192 -95
  93. package/lib/module/components/ButtonGroup/ButtonGroup.js +2 -2
  94. package/lib/module/components/Card/Card.js +2 -1
  95. package/lib/module/components/CardCTA/CardCTA.js +2 -2
  96. package/lib/module/components/CardProviderInfo/CardProviderInfo.js +2 -2
  97. package/lib/module/components/Carousel/Carousel.js +3 -2
  98. package/lib/module/components/Checkbox/Checkbox.js +2 -1
  99. package/lib/module/components/ChipGroup/ChipGroup.js +2 -2
  100. package/lib/module/components/ChipSelect/ChipSelect.js +2 -1
  101. package/lib/module/components/DebitCard/DebitCard.js +2 -2
  102. package/lib/module/components/Disclaimer/Disclaimer.js +2 -1
  103. package/lib/module/components/Divider/Divider.js +2 -1
  104. package/lib/module/components/Drawer/Drawer.js +109 -48
  105. package/lib/module/components/EmptyState/EmptyState.js +2 -1
  106. package/lib/module/components/FilterBar/FilterBar.js +2 -2
  107. package/lib/module/components/Form/Form.js +2 -1
  108. package/lib/module/components/FormField/FormField.js +3 -2
  109. package/lib/module/components/HStack/HStack.js +2 -2
  110. package/lib/module/components/HoldingsCard/HoldingsCard.js +2 -1
  111. package/lib/module/components/IconButton/IconButton.js +120 -130
  112. package/lib/module/components/IconCapsule/IconCapsule.js +60 -57
  113. package/lib/module/components/InputSearch/InputSearch.js +7 -3
  114. package/lib/module/components/LazyList/LazyList.js +2 -2
  115. package/lib/module/components/LinearMeter/LinearMeter.js +3 -2
  116. package/lib/module/components/ListGroup/ListGroup.js +2 -2
  117. package/lib/module/components/ListItem/ListItem.js +194 -146
  118. package/lib/module/components/MediaCard/MediaCard.js +4 -2
  119. package/lib/module/components/MerchantProfile/MerchantProfile.js +2 -1
  120. package/lib/module/components/MoneyValue/MoneyValue.js +2 -1
  121. package/lib/module/components/NavArrow/NavArrow.js +82 -58
  122. package/lib/module/components/NoteInput/NoteInput.js +2 -1
  123. package/lib/module/components/Nudge/Nudge.js +2 -2
  124. package/lib/module/components/Numpad/Numpad.js +2 -1
  125. package/lib/module/components/OTP/OTP.js +2 -2
  126. package/lib/module/components/PaymentFeedback/PaymentFeedback.js +2 -1
  127. package/lib/module/components/Popup/Popup.js +2 -1
  128. package/lib/module/components/ProductLabel/ProductLabel.js +2 -1
  129. package/lib/module/components/ProgressBadge/ProgressBadge.js +2 -1
  130. package/lib/module/components/RadioButton/RadioButton.js +2 -1
  131. package/lib/module/components/RechargeCard/RechargeCard.js +2 -1
  132. package/lib/module/components/Screen/Screen.js +2 -2
  133. package/lib/module/components/Section/Section.js +503 -169
  134. package/lib/module/components/SegmentedControl/SegmentedControl.js +3 -2
  135. package/lib/module/components/StatItem/StatItem.js +2 -1
  136. package/lib/module/components/StatusHero/StatusHero.js +2 -1
  137. package/lib/module/components/Stepper/Step.js +2 -1
  138. package/lib/module/components/Stepper/StepLabel.js +2 -1
  139. package/lib/module/components/Stepper/Stepper.js +2 -1
  140. package/lib/module/components/SupportText/SupportText.js +2 -1
  141. package/lib/module/components/SupportText/SupportTextIcon.js +2 -1
  142. package/lib/module/components/SwappableAmount/SwappableAmount.js +2 -1
  143. package/lib/module/components/Tabs/TabItem.js +2 -1
  144. package/lib/module/components/Tabs/Tabs.js +2 -1
  145. package/lib/module/components/Text/Text.js +2 -1
  146. package/lib/module/components/TextInput/TextInput.js +3 -3
  147. package/lib/module/components/ThreadHero/ThreadHero.js +2 -1
  148. package/lib/module/components/Title/Title.js +2 -1
  149. package/lib/module/components/Toast/Toast.js +2 -1
  150. package/lib/module/components/Toggle/Toggle.js +2 -1
  151. package/lib/module/components/Tooltip/Tooltip.js +2 -1
  152. package/lib/module/components/TransactionBubble/TransactionBubble.js +2 -2
  153. package/lib/module/components/TransactionDetails/TransactionDetails.js +3 -3
  154. package/lib/module/components/TransactionStatus/TransactionStatus.js +3 -2
  155. package/lib/module/components/UpiHandle/UpiHandle.js +147 -113
  156. package/lib/module/components/VStack/VStack.js +2 -2
  157. package/lib/module/design-tokens/figma-variables-resolver.js +21 -3
  158. package/lib/module/icons/registry.js +1 -1
  159. package/lib/module/utils/react-utils.js +16 -0
  160. package/lib/typescript/src/components/Avatar/Avatar.d.ts +11 -17
  161. package/lib/typescript/src/components/BottomNavItem/BottomNavItem.d.ts +12 -8
  162. package/lib/typescript/src/components/Button/Button.d.ts +18 -1
  163. package/lib/typescript/src/components/IconButton/IconButton.d.ts +12 -29
  164. package/lib/typescript/src/components/IconCapsule/IconCapsule.d.ts +10 -18
  165. package/lib/typescript/src/components/InputSearch/InputSearch.d.ts +8 -3
  166. package/lib/typescript/src/components/ListItem/ListItem.d.ts +14 -1
  167. package/lib/typescript/src/components/NavArrow/NavArrow.d.ts +12 -11
  168. package/lib/typescript/src/components/Section/Section.d.ts +43 -48
  169. package/lib/typescript/src/components/UpiHandle/UpiHandle.d.ts +13 -12
  170. package/lib/typescript/src/icons/registry.d.ts +1 -1
  171. package/lib/typescript/src/utils/react-utils.d.ts +15 -0
  172. package/package.json +4 -6
  173. package/src/components/Accordion/Accordion.tsx +2 -2
  174. package/src/components/ActionFooter/ActionFooter.tsx +2 -2
  175. package/src/components/ActionTile/ActionTile.tsx +2 -1
  176. package/src/components/AmountInput/AmountInput.tsx +2 -1
  177. package/src/components/AppBar/AppBar.tsx +2 -2
  178. package/src/components/Avatar/Avatar.tsx +229 -158
  179. package/src/components/AvatarGroup/AvatarGroup.tsx +2 -2
  180. package/src/components/Badge/Badge.tsx +2 -1
  181. package/src/components/Balance/Balance.tsx +2 -1
  182. package/src/components/BottomNav/BottomNav.tsx +2 -1
  183. package/src/components/BottomNavItem/BottomNavItem.tsx +159 -88
  184. package/src/components/Button/Button.tsx +228 -101
  185. package/src/components/ButtonGroup/ButtonGroup.tsx +2 -2
  186. package/src/components/Card/Card.tsx +2 -1
  187. package/src/components/CardCTA/CardCTA.tsx +2 -2
  188. package/src/components/CardProviderInfo/CardProviderInfo.tsx +2 -2
  189. package/src/components/Carousel/Carousel.tsx +3 -2
  190. package/src/components/Checkbox/Checkbox.tsx +2 -1
  191. package/src/components/ChipGroup/ChipGroup.tsx +2 -2
  192. package/src/components/ChipSelect/ChipSelect.tsx +2 -1
  193. package/src/components/DebitCard/DebitCard.tsx +2 -2
  194. package/src/components/Disclaimer/Disclaimer.tsx +2 -1
  195. package/src/components/Divider/Divider.tsx +2 -1
  196. package/src/components/Drawer/Drawer.tsx +124 -58
  197. package/src/components/EmptyState/EmptyState.tsx +2 -1
  198. package/src/components/FilterBar/FilterBar.tsx +2 -2
  199. package/src/components/Form/Form.tsx +2 -1
  200. package/src/components/FormField/FormField.tsx +3 -2
  201. package/src/components/HStack/HStack.tsx +2 -2
  202. package/src/components/HoldingsCard/HoldingsCard.tsx +2 -1
  203. package/src/components/IconButton/IconButton.tsx +154 -126
  204. package/src/components/IconCapsule/IconCapsule.tsx +73 -54
  205. package/src/components/InputSearch/InputSearch.tsx +19 -5
  206. package/src/components/LazyList/LazyList.tsx +2 -2
  207. package/src/components/LinearMeter/LinearMeter.tsx +3 -2
  208. package/src/components/ListGroup/ListGroup.tsx +2 -2
  209. package/src/components/ListItem/ListItem.tsx +257 -187
  210. package/src/components/MediaCard/MediaCard.tsx +2 -1
  211. package/src/components/MerchantProfile/MerchantProfile.tsx +2 -1
  212. package/src/components/MoneyValue/MoneyValue.tsx +2 -1
  213. package/src/components/NavArrow/NavArrow.tsx +91 -58
  214. package/src/components/NoteInput/NoteInput.tsx +2 -1
  215. package/src/components/Nudge/Nudge.tsx +2 -2
  216. package/src/components/Numpad/Numpad.tsx +2 -1
  217. package/src/components/OTP/OTP.tsx +2 -2
  218. package/src/components/PaymentFeedback/PaymentFeedback.tsx +2 -1
  219. package/src/components/Popup/Popup.tsx +2 -1
  220. package/src/components/ProductLabel/ProductLabel.tsx +2 -1
  221. package/src/components/ProgressBadge/ProgressBadge.tsx +2 -2
  222. package/src/components/RadioButton/RadioButton.tsx +2 -1
  223. package/src/components/RechargeCard/RechargeCard.tsx +2 -1
  224. package/src/components/Screen/Screen.tsx +2 -2
  225. package/src/components/Section/Section.tsx +672 -176
  226. package/src/components/SegmentedControl/SegmentedControl.tsx +3 -2
  227. package/src/components/StatItem/StatItem.tsx +2 -1
  228. package/src/components/StatusHero/StatusHero.tsx +2 -1
  229. package/src/components/Stepper/Step.tsx +2 -1
  230. package/src/components/Stepper/StepLabel.tsx +2 -1
  231. package/src/components/Stepper/Stepper.tsx +2 -1
  232. package/src/components/SupportText/SupportText.tsx +2 -1
  233. package/src/components/SupportText/SupportTextIcon.tsx +2 -1
  234. package/src/components/SwappableAmount/SwappableAmount.tsx +2 -1
  235. package/src/components/Tabs/TabItem.tsx +2 -1
  236. package/src/components/Tabs/Tabs.tsx +2 -1
  237. package/src/components/Text/Text.tsx +2 -1
  238. package/src/components/TextInput/TextInput.tsx +3 -3
  239. package/src/components/ThreadHero/ThreadHero.tsx +2 -1
  240. package/src/components/Title/Title.tsx +2 -1
  241. package/src/components/Toast/Toast.tsx +2 -1
  242. package/src/components/Toggle/Toggle.tsx +2 -1
  243. package/src/components/Tooltip/Tooltip.tsx +2 -1
  244. package/src/components/TransactionBubble/TransactionBubble.tsx +2 -2
  245. package/src/components/TransactionDetails/TransactionDetails.tsx +3 -3
  246. package/src/components/TransactionStatus/TransactionStatus.tsx +3 -2
  247. package/src/components/UpiHandle/UpiHandle.tsx +193 -125
  248. package/src/components/VStack/VStack.tsx +2 -2
  249. package/src/design-tokens/figma-variables-resolver.ts +21 -3
  250. package/src/icons/registry.ts +1 -1
  251. package/src/utils/react-utils.ts +16 -0
  252. package/lib/typescript/App.d.ts +0 -2
  253. package/lib/typescript/index.d.ts +0 -2
  254. package/lib/typescript/metro.config.d.ts +0 -78
  255. package/lib/typescript/react-native.config.d.ts +0 -4
@@ -1,18 +1,115 @@
1
- import React from 'react'
2
- import { Pressable, View, Image, Text, type ImageSourcePropType, type ViewStyle, type TextStyle, type ImageStyle } from 'react-native'
1
+ import React, { useCallback, useMemo, useRef, useState } from 'react'
2
+ import {
3
+ Pressable,
4
+ View,
5
+ Image,
6
+ Text,
7
+ Platform,
8
+ type ImageSourcePropType,
9
+ type ViewStyle,
10
+ type TextStyle,
11
+ type ImageStyle,
12
+ type PressableStateCallbackType,
13
+ type StyleProp,
14
+ } from 'react-native'
3
15
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
16
+ import { EMPTY_MODES } from '../../utils/react-utils'
4
17
 
5
18
  const avatarImage = require('./31595e70c4181263f9971590224b12934b280c9b.png')
6
19
 
20
+ // ---------------------------------------------------------------------------
21
+ // Module-scope constants — never re-allocated per render.
22
+ // ---------------------------------------------------------------------------
23
+
24
+ const IS_WEB = Platform.OS === 'web'
25
+ const IS_IOS = Platform.OS === 'ios'
26
+ const PRESS_DELAY = IS_IOS ? 130 : 0
27
+
28
+ // Pressed visual is applied through Pressable's style callback so the host
29
+ // view updates without scheduling a React render.
30
+ const pressedOverlayStyle: ViewStyle = { transform: [{ scale: 0.98 }] }
31
+ const focusOverlayStyle: ViewStyle = { borderColor: '#222', borderWidth: 1 }
32
+
33
+ const imageContainerStyle: ViewStyle = {
34
+ width: '100%',
35
+ height: '100%',
36
+ overflow: 'hidden',
37
+ }
38
+
39
+ const imageBaseStyle: ImageStyle = {
40
+ width: '100%',
41
+ height: '100%',
42
+ }
43
+
44
+ const monogramContainerStyle: ViewStyle = {
45
+ width: '100%',
46
+ height: '100%',
47
+ justifyContent: 'center',
48
+ alignItems: 'center',
49
+ paddingVertical: 7,
50
+ paddingHorizontal: 5,
51
+ }
52
+
53
+ // ---------------------------------------------------------------------------
54
+ // Token resolution — collapsed into one useMemo per (modes, style) tuple.
55
+ // ---------------------------------------------------------------------------
56
+
57
+ interface AvatarTokens {
58
+ containerStyle: ViewStyle;
59
+ imageContainerStyle: ViewStyle;
60
+ imageStyle: ImageStyle;
61
+ monogramTextStyle: TextStyle;
62
+ }
63
+
64
+ function resolveAvatarTokens(modes: Record<string, any>, isMonogram: boolean): AvatarTokens {
65
+ const size = (getVariableByName('avatar/size', modes) || 29) as number
66
+ const radiusRaw = (getVariableByName('avatar/radius', modes) || 9999) as number
67
+ const backgroundColor = getVariableByName('avatar/background', modes) || '#dbcfff'
68
+ const borderColor = getVariableByName('avatar/border/color', modes) || 'rgba(255,255,255,0)'
69
+ const borderSize = (getVariableByName('avatar/border/size', modes) || 1) as number
70
+ const labelColor = getVariableByName('avatar/label/color', modes) || '#5c00b5'
71
+ const labelFontSize = (getVariableByName('avatar/label/fontSize', modes) || 12) as number
72
+ const labelLineHeight = (getVariableByName('avatar/label/lineHeight', modes) || 14) as number
73
+ const labelFontWeightRaw = getVariableByName('avatar/label/fontWeight', modes) || 500
74
+ const labelFontWeight =
75
+ typeof labelFontWeightRaw === 'number' ? labelFontWeightRaw.toString() : labelFontWeightRaw
76
+ const labelFontFamily = (getVariableByName('avatar/label/fontFamily', modes) || 'Inter') as string
77
+
78
+ // 9999 is the design-token sentinel for "perfect circle"
79
+ const borderRadius = radiusRaw === 9999 ? size / 2 : radiusRaw
80
+
81
+ return {
82
+ containerStyle: {
83
+ width: size,
84
+ height: size,
85
+ borderRadius,
86
+ borderWidth: borderSize,
87
+ borderColor: borderColor as string,
88
+ overflow: 'hidden',
89
+ ...(isMonogram ? { backgroundColor: backgroundColor as string } : {}),
90
+ },
91
+ imageContainerStyle: {
92
+ ...imageContainerStyle,
93
+ borderRadius,
94
+ },
95
+ imageStyle: {
96
+ ...imageBaseStyle,
97
+ borderRadius,
98
+ },
99
+ monogramTextStyle: {
100
+ fontSize: labelFontSize,
101
+ lineHeight: labelLineHeight,
102
+ fontWeight: labelFontWeight as TextStyle['fontWeight'],
103
+ fontFamily: labelFontFamily,
104
+ color: labelColor as string,
105
+ textAlign: 'center',
106
+ },
107
+ }
108
+ }
109
+
7
110
  /**
8
111
  * Avatar component that displays either an image or a monogram.
9
- *
10
- * This component supports two styles:
11
- * - Image: Displays a user's profile picture
12
- * - Monogram: Displays user initials in a circular background
13
- *
14
- * All styling values are resolved from Figma design tokens using the provided modes.
15
- *
112
+ *
16
113
  * @component
17
114
  * @param {Object} props - Component props
18
115
  * @param {string} [props.monogram="MS"] - The initials to display when style is "Monogram"
@@ -20,19 +117,18 @@ const avatarImage = require('./31595e70c4181263f9971590224b12934b280c9b.png')
20
117
  * @param {Object} [props.modes={}] - Mode configuration for design tokens (e.g., {"Avatar Size": "M"})
21
118
  * @param {string} [props.imageSource] - Optional image source for Image style (defaults to built-in image)
22
119
  * @param {string} [props.accessibilityLabel] - Accessibility label for screen readers. If not provided, uses monogram or "User avatar"
23
- *
24
- * @example
25
- * ```jsx
26
- * // Image style
27
- * <Avatar style="Image" modes={{"Avatar Size": "M"}} />
28
- *
29
- * // Monogram style
30
- * <Avatar style="Monogram" monogram="JD" modes={{"Avatar Size": "L"}} />
31
- * ```
120
+ *
121
+ * Performance notes:
122
+ * - All token reads are folded into a single `useMemo([modes, isMonogram])`.
123
+ * - Press visual goes through Pressable's `({ pressed })` style callback so
124
+ * a scroll-cancelled touch never schedules a React render. iOS gets
125
+ * `unstable_pressDelay={130}` for additional safety inside scrollables.
126
+ * - Focus state stays in React (it's a sustained visual) but the setter is
127
+ * gated to web only, where focus events actually fire.
32
128
  */
33
129
  export type AvatarProps = {
34
130
  monogram?: string;
35
- style?: "Image" | "Monogram";
131
+ style?: 'Image' | 'Monogram';
36
132
  modes?: Record<string, any>;
37
133
  imageSource?: ImageSourcePropType | string;
38
134
  accessibilityLabel?: string;
@@ -41,169 +137,144 @@ export type AvatarProps = {
41
137
  } & Omit<React.ComponentProps<typeof View>, 'style' | 'accessibilityRole' | 'accessibilityLabel'>;
42
138
 
43
139
  function Avatar({
44
- monogram = "MS",
45
- style = "Image",
46
- modes = {},
140
+ monogram = 'MS',
141
+ style = 'Image',
142
+ modes = EMPTY_MODES,
47
143
  imageSource,
48
- accessibilityLabel,
144
+ // accessibilityLabel is accepted on the type for API back-compat but the
145
+ // component intentionally renders `accessibilityLabel={undefined}` on the
146
+ // wrapper (the inner Text/Image carry the label instead).
147
+ accessibilityLabel: _accessibilityLabel,
49
148
  ...rest
50
149
  }: AvatarProps) {
51
- // Resolve design tokens using the provided modes
52
- const size = getVariableByName('avatar/size', modes) || 29
53
- const radius = getVariableByName('avatar/radius', modes) || 9999
54
- const backgroundColor = getVariableByName('avatar/background', modes) || '#dbcfff'
55
- const borderColor = getVariableByName('avatar/border/color', modes) || 'rgba(255,255,255,0)'
56
- const borderSize = getVariableByName('avatar/border/size', modes) || 1
57
- const labelColor = getVariableByName('avatar/label/color', modes) || '#5c00b5'
58
- const labelFontSize = getVariableByName('avatar/label/fontSize', modes) || 12
59
- const labelLineHeight = getVariableByName('avatar/label/lineHeight', modes) || 14
60
- const labelFontWeight = getVariableByName('avatar/label/fontWeight', modes) || 500
61
- const labelFontFamily = getVariableByName('avatar/label/fontFamily', modes) || 'Inter'
62
-
63
- // Convert radius to React Native format (if 9999, use size/2 for perfect circle)
64
- const borderRadius = radius === 9999 ? size / 2 : radius
65
-
66
- // Container style
67
- const containerStyle: ViewStyle = {
68
- width: size,
69
- height: size,
70
- borderRadius: borderRadius,
71
- borderWidth: borderSize,
72
- borderColor: borderColor,
73
- overflow: 'hidden',
74
- ...(style === 'Monogram' ? { backgroundColor: backgroundColor } : {})
75
- }
150
+ const isMonogram = style === 'Monogram'
151
+ const tokens = useMemo(() => resolveAvatarTokens(modes, isMonogram), [modes, isMonogram])
76
152
 
77
- // Image container style
78
- const imageContainerStyle: ViewStyle = {
79
- width: '100%',
80
- height: '100%',
81
- borderRadius: borderRadius,
82
- overflow: 'hidden',
83
- }
153
+ // Focus is a sustained visible state — keep mirroring on web; gate the
154
+ // setter so it never fires on native (where focus events don't fire on
155
+ // these elements anyway).
156
+ const [isFocused, setIsFocused] = useState(false)
84
157
 
85
- // Image style
86
- const imageStyle: ImageStyle = {
87
- width: '100%',
88
- height: '100%',
89
- borderRadius: borderRadius,
90
- }
158
+ // Mirror user handlers in a ref so our wrappers can stay referentially
159
+ // stable across renders.
160
+ const userHandlersRef = useRef<{
161
+ onPressIn?: (e: any) => void
162
+ onPressOut?: (e: any) => void
163
+ onFocus?: (e: any) => void
164
+ onBlur?: (e: any) => void
165
+ }>({})
166
+ userHandlersRef.current.onPressIn = (rest as any)?.onPressIn
167
+ userHandlersRef.current.onPressOut = (rest as any)?.onPressOut
168
+ userHandlersRef.current.onFocus = (rest as any)?.onFocus
169
+ userHandlersRef.current.onBlur = (rest as any)?.onBlur
91
170
 
92
- // Monogram container style
93
- const monogramContainerStyle: ViewStyle = {
94
- width: '100%',
95
- height: '100%',
96
- justifyContent: 'center',
97
- alignItems: 'center',
98
- paddingVertical: 7,
99
- paddingHorizontal: 5,
100
- }
171
+ const handlePressIn = useCallback((e: any) => {
172
+ userHandlersRef.current.onPressIn?.(e)
173
+ }, [])
174
+ const handlePressOut = useCallback((e: any) => {
175
+ userHandlersRef.current.onPressOut?.(e)
176
+ }, [])
177
+ const handleFocus = useCallback((e: any) => {
178
+ if (IS_WEB) setIsFocused(true)
179
+ userHandlersRef.current.onFocus?.(e)
180
+ }, [])
181
+ const handleBlur = useCallback((e: any) => {
182
+ if (IS_WEB) setIsFocused(false)
183
+ userHandlersRef.current.onBlur?.(e)
184
+ }, [])
101
185
 
102
- // Monogram text style
103
- const monogramTextStyle: TextStyle = {
104
- fontSize: labelFontSize,
105
- lineHeight: labelLineHeight,
106
- fontWeight: labelFontWeight,
107
- fontFamily: labelFontFamily,
108
- color: labelColor,
109
- textAlign: 'center',
110
- }
186
+ const pressableStyle = useCallback(
187
+ ({ pressed }: PressableStateCallbackType): StyleProp<ViewStyle> => [
188
+ tokens.containerStyle,
189
+ pressed ? pressedOverlayStyle : null,
190
+ isFocused ? focusOverlayStyle : null,
191
+ ],
192
+ [tokens.containerStyle, isFocused]
193
+ )
111
194
 
112
- // Generate default accessibility label
113
- const defaultAccessibilityLabel = accessibilityLabel ||
114
- (style === "Monogram" ? `Avatar with initials ${monogram}` : "User avatar")
115
- // Interaction placeholders for press/focus
116
- const [isPressed, setIsPressed] = React.useState(false)
117
- const [isFocused, setIsFocused] = React.useState(false)
118
- const pressedStyle = isPressed ? { transform: [{ scale: 0.98 }] } : null
119
- const focusStyle = isFocused ? { borderColor: '#222', borderWidth: 1 } : null
120
-
121
- if (style === "Monogram") {
122
- const Wrapper: any = (rest as any)?.onPress ? Pressable : View
195
+ const staticContainerStyle = useMemo<StyleProp<ViewStyle>>(
196
+ () => [tokens.containerStyle, isFocused ? focusOverlayStyle : null],
197
+ [tokens.containerStyle, isFocused]
198
+ )
199
+
200
+ // The inner content varies; everything else (wrapper, handlers, style) is shared.
201
+ const innerContent = isMonogram ? (
202
+ <View style={monogramContainerStyle}>
203
+ <Text
204
+ style={tokens.monogramTextStyle}
205
+ accessibilityElementsHidden={true}
206
+ importantForAccessibility="no"
207
+ >
208
+ {monogram}
209
+ </Text>
210
+ </View>
211
+ ) : (
212
+ <ImageContent
213
+ tokens={tokens}
214
+ imageSource={imageSource}
215
+ />
216
+ )
217
+
218
+ const isPressable = !!(rest as any)?.onPress
219
+
220
+ if (isPressable) {
123
221
  return (
124
- <Wrapper
125
- style={[containerStyle, pressedStyle, focusStyle]}
222
+ <Pressable
223
+ style={pressableStyle}
126
224
  accessibilityRole="image"
127
225
  accessibilityLabel={undefined}
128
- onPressIn={(e: any) => {
129
- setIsPressed(true)
130
- ; (rest as any)?.onPressIn?.(e)
131
- }}
132
- onPressOut={(e: any) => {
133
- setIsPressed(false)
134
- ; (rest as any)?.onPressOut?.(e)
135
- }}
136
- onFocus={(e: any) => {
137
- setIsFocused(true)
138
- ; (rest as any)?.onFocus?.(e)
139
- }}
140
- onBlur={(e: any) => {
141
- setIsFocused(false)
142
- ; (rest as any)?.onBlur?.(e)
143
- }}
226
+ onPressIn={handlePressIn}
227
+ onPressOut={handlePressOut}
228
+ onFocus={handleFocus}
229
+ onBlur={handleBlur}
230
+ unstable_pressDelay={PRESS_DELAY}
144
231
  {...rest}
145
232
  >
146
- <View style={monogramContainerStyle}>
147
- <Text
148
- style={monogramTextStyle}
149
- accessibilityElementsHidden={true}
150
- importantForAccessibility="no"
151
- >
152
- {monogram}
153
- </Text>
154
- </View>
155
- </Wrapper>
233
+ {innerContent}
234
+ </Pressable>
156
235
  )
157
236
  }
158
237
 
159
- // Normalize imageSource: if it's a string (URL), convert to { uri: string } format
160
- // Otherwise, use it as-is (could be require() result or already { uri: ... } object)
161
- const normalizedImageSource = React.useMemo(() => {
238
+ return (
239
+ <View
240
+ style={staticContainerStyle}
241
+ accessibilityRole="image"
242
+ accessibilityLabel={undefined}
243
+ {...rest}
244
+ >
245
+ {innerContent}
246
+ </View>
247
+ )
248
+ }
249
+
250
+ // Memoize the image normalization so a string URL that doesn't change
251
+ // doesn't keep producing a new `{ uri }` object every render.
252
+ function ImageContent({
253
+ tokens,
254
+ imageSource,
255
+ }: {
256
+ tokens: AvatarTokens;
257
+ imageSource?: ImageSourcePropType | string;
258
+ }) {
259
+ const normalizedImageSource = useMemo(() => {
162
260
  if (!imageSource) return avatarImage
163
261
  if (typeof imageSource === 'string') {
164
- // If it's a string, treat it as a URL
165
262
  return { uri: imageSource }
166
263
  }
167
- // Otherwise, use it as-is (could be require() result or { uri: ... } object)
168
264
  return imageSource
169
265
  }, [imageSource])
170
266
 
171
- const Wrapper: any = (rest as any)?.onPress ? Pressable : View
172
267
  return (
173
- <Wrapper
174
- style={[containerStyle, pressedStyle, focusStyle]}
175
- accessibilityRole="image"
176
- accessibilityLabel={undefined}
177
- onPressIn={(e: any) => {
178
- setIsPressed(true)
179
- ; (rest as any)?.onPressIn?.(e)
180
- }}
181
- onPressOut={(e: any) => {
182
- setIsPressed(false)
183
- ; (rest as any)?.onPressOut?.(e)
184
- }}
185
- onFocus={(e: any) => {
186
- setIsFocused(true)
187
- ; (rest as any)?.onFocus?.(e)
188
- }}
189
- onBlur={(e: any) => {
190
- setIsFocused(false)
191
- ; (rest as any)?.onBlur?.(e)
192
- }}
193
- {...rest}
194
- >
195
- <View style={imageContainerStyle}>
196
- <Image
197
- source={normalizedImageSource}
198
- resizeMode="cover"
199
- style={imageStyle}
200
- accessibilityElementsHidden={true}
201
- importantForAccessibility="no"
202
- />
203
- </View>
204
- </Wrapper>
268
+ <View style={tokens.imageContainerStyle}>
269
+ <Image
270
+ source={normalizedImageSource}
271
+ resizeMode="cover"
272
+ style={tokens.imageStyle}
273
+ accessibilityElementsHidden={true}
274
+ importantForAccessibility="no"
275
+ />
276
+ </View>
205
277
  )
206
278
  }
207
279
 
208
- export default Avatar
209
-
280
+ export default React.memo(Avatar)
@@ -3,7 +3,7 @@ import { View, Platform, type ViewStyle } from 'react-native';
3
3
  import MaskedView from '@react-native-masked-view/masked-view';
4
4
  import Svg, { Path } from 'react-native-svg';
5
5
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
6
- import { flattenChildren } from '../../utils/react-utils';
6
+ import { EMPTY_MODES, flattenChildren } from '../../utils/react-utils';
7
7
 
8
8
  type AvatarGroupProps = {
9
9
  modes?: Record<string, any>;
@@ -11,7 +11,7 @@ type AvatarGroupProps = {
11
11
  style?: ViewStyle;
12
12
  } & React.ComponentProps<typeof View>;
13
13
 
14
- function AvatarGroup({ modes = {}, children, style, ...rest }: AvatarGroupProps) {
14
+ function AvatarGroup({ modes = EMPTY_MODES, children, style, ...rest }: AvatarGroupProps) {
15
15
  const gap = getVariableByName('avatarGroup/gap', modes) || 0;
16
16
 
17
17
 
@@ -7,6 +7,7 @@ import {
7
7
  type TextStyle,
8
8
  } from 'react-native'
9
9
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
10
+ import { EMPTY_MODES } from '../../utils/react-utils'
10
11
 
11
12
  type BadgeProps = {
12
13
  /** Visible label text shown inside the badge */
@@ -22,7 +23,7 @@ type BadgeProps = {
22
23
 
23
24
  function Badge({
24
25
  label = 'Label',
25
- modes = {},
26
+ modes = EMPTY_MODES,
26
27
  onPress,
27
28
  accessibilityLabel,
28
29
  style,
@@ -2,6 +2,7 @@ import React from 'react'
2
2
  import { View, Text, type StyleProp, type ViewStyle, type TextStyle } from 'react-native'
3
3
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
4
4
  import MoneyValue from '../MoneyValue/MoneyValue'
5
+ import { EMPTY_MODES } from '../../utils/react-utils'
5
6
 
6
7
  export type BalanceProps = {
7
8
  /**
@@ -47,7 +48,7 @@ function Balance({
47
48
  title = "Total owed to people",
48
49
  amount = "500",
49
50
  currency = "₹",
50
- modes = {},
51
+ modes = EMPTY_MODES,
51
52
  style,
52
53
  children,
53
54
  }: BalanceProps) {
@@ -3,6 +3,7 @@ import { View, type StyleProp, type ViewStyle } from 'react-native'
3
3
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
4
4
  import { useTokens } from '../../design-tokens/JFSThemeProvider'
5
5
  import BottomNavItem from '../BottomNavItem/BottomNavItem'
6
+ import { EMPTY_MODES } from '../../utils/react-utils'
6
7
 
7
8
  type BottomNavProps = {
8
9
  /**
@@ -41,7 +42,7 @@ function BottomNav({
41
42
  value,
42
43
  onChange,
43
44
  children,
44
- modes: propModes = {},
45
+ modes: propModes = EMPTY_MODES,
45
46
  style,
46
47
  accessibilityLabel = undefined,
47
48
  accessibilityHint,