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,13 +1,33 @@
1
- import React, { useState } from 'react'
2
- import { Pressable, View, Text, Image, type ViewStyle, type TextStyle, type ImageStyle, type ImageSourcePropType } from 'react-native'
1
+ import React, { useCallback, useMemo, useRef, useState } from 'react'
2
+ import {
3
+ Pressable,
4
+ View,
5
+ Text,
6
+ Image,
7
+ Platform,
8
+ type ViewStyle,
9
+ type TextStyle,
10
+ type ImageStyle,
11
+ type ImageSourcePropType,
12
+ type StyleProp,
13
+ type PressableStateCallbackType,
14
+ } from 'react-native'
3
15
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
4
16
  import { useTokens } from '../../design-tokens/JFSThemeProvider'
17
+ import { EMPTY_MODES } from '../../utils/react-utils'
5
18
  import Icon from '../../icons/Icon'
6
19
 
7
20
  // Default static asset from the component folder.
8
21
  // Consumers can override the image via the `avatarSource` prop if needed.
9
22
  const DEFAULT_AVATAR_IMAGE = require('./Image.png')
10
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
+ const pressedOverlayStyle: ViewStyle = { transform: [{ scale: 0.98 }] }
29
+ const focusOverlayStyle: ViewStyle = { borderWidth: 1, borderColor: '#222' }
30
+
11
31
  type UpiHandleProps = {
12
32
  label?: string;
13
33
  modes?: Record<string, any>;
@@ -21,160 +41,178 @@ type UpiHandleProps = {
21
41
  disabled?: boolean;
22
42
  } & Omit<React.ComponentProps<typeof View>, 'style' | 'accessibilityRole' | 'accessibilityLabel' | 'accessibilityHint'>;
23
43
 
44
+ interface UpiHandleTokens {
45
+ containerStyle: ViewStyle;
46
+ avatarStyle: ImageStyle;
47
+ labelStyle: TextStyle;
48
+ iconColor: string;
49
+ iconSize: number;
50
+ iconPlaceholderStyle: ViewStyle;
51
+ }
52
+
53
+ function resolveUpiHandleTokens(modes: Record<string, any>): UpiHandleTokens {
54
+ const backgroundColor = (getVariableByName('upiHandle/background', modes) || '#f5f5f5') as string
55
+ const radius = (getVariableByName('upiHandle/radius', modes) || 99999) as number
56
+ const paddingLeft = (getVariableByName('upiHandle/padding/left', modes) || 4) as number
57
+ const paddingRight = (getVariableByName('upiHandle/padding/right', modes) || 14) as number
58
+ const paddingVertical = (getVariableByName('upiHandle/padding/vertical', modes) || 3) as number
59
+ const gap = (getVariableByName('upiHandle/gap', modes) || 6) as number
60
+
61
+ const avatarSize = (getVariableByName('upiHandle/image/size', modes) || 23) as number
62
+ const avatarRadius = (getVariableByName('upiHandle/image/radius', modes) || 99999) as number
63
+
64
+ const labelColor = (getVariableByName('upiHandle/label/color', modes) || '#0d0d0f') as string
65
+ const labelFontSize = (getVariableByName('upiHandle/label/fontSize', modes) || 12) as number
66
+ const labelLineHeight = (getVariableByName('upiHandle/label/lineHeight', modes) || 23) as number
67
+ const labelFontFamily = (getVariableByName('upiHandle/label/fontFamily', modes) || 'System') as string
68
+ const labelFontWeightRaw = getVariableByName('upiHandle/label/fontWeight', modes) || 500
69
+ const labelFontWeight =
70
+ typeof labelFontWeightRaw === 'number' ? labelFontWeightRaw.toString() : labelFontWeightRaw
71
+
72
+ const iconColor = (getVariableByName('upiHandle/icon/color', modes) || '#0d0d0f') as string
73
+ const iconSize = (getVariableByName('upiHandle/icon/size', modes) || 12) as number
74
+
75
+ return {
76
+ containerStyle: {
77
+ flexDirection: 'row',
78
+ alignItems: 'center',
79
+ justifyContent: 'center',
80
+ backgroundColor,
81
+ paddingLeft,
82
+ paddingRight,
83
+ paddingVertical,
84
+ borderRadius: radius,
85
+ gap,
86
+ },
87
+ avatarStyle: {
88
+ width: avatarSize,
89
+ height: avatarSize,
90
+ borderRadius: avatarRadius,
91
+ overflow: 'hidden',
92
+ },
93
+ labelStyle: {
94
+ color: labelColor,
95
+ fontSize: labelFontSize,
96
+ lineHeight: labelLineHeight,
97
+ fontFamily: labelFontFamily,
98
+ fontWeight: labelFontWeight as TextStyle['fontWeight'],
99
+ },
100
+ iconColor,
101
+ iconSize,
102
+ iconPlaceholderStyle: {
103
+ width: iconSize,
104
+ height: iconSize,
105
+ },
106
+ }
107
+ }
108
+
24
109
  /**
25
110
  * UpiHandle pill that mirrors the Figma "UPI Handle" component.
26
111
  *
27
- * Layout:
28
- * - Circular image/avatar on the left
29
- * - Label text in the center
30
- * - Optional QR-code style icon on the right
31
- *
32
- * All visual styling is resolved from Figma variables via `getVariableByName`
33
- * using a `modes` configuration object, matching the rest of this library.
34
- *
35
112
  * @component
36
113
  * @param {Object} props
37
114
  * @param {string} [props.label="Label"] - UPI handle text to display.
38
115
  * @param {Object} [props.modes={}] - Modes object passed directly to `getVariableByName`.
39
116
  * @param {boolean} [props.showIcon=true] - Toggles the trailing icon visibility.
40
- * @param {string} [props.iconName='ic_scan_qr_code'] - Icon name from the actions set (e.g. 'ic_qr_code', 'ic_scan_qr_code').
117
+ * @param {string} [props.iconName='ic_scan_qr_code'] - Icon name from the actions set.
41
118
  * @param {ImageSourcePropType} [props.avatarSource] - Optional custom image source for the avatar.
42
119
  * @param {Function} [props.onClick] - Click/tap handler. Works as an alias for `onPress`.
43
- * @param {string} [props.accessibilityLabel] - Accessibility label for screen readers. If not provided, uses label
120
+ * @param {string} [props.accessibilityLabel] - Accessibility label for screen readers
44
121
  * @param {string} [props.accessibilityHint] - Additional accessibility hint for screen readers
122
+ *
123
+ * Performance notes:
124
+ * - Token reads collapsed into a single `useMemo([modes])`.
125
+ * - Press visual goes through Pressable's `({ pressed })` style callback so
126
+ * a scroll-cancelled touch never schedules a React render. iOS gets
127
+ * `unstable_pressDelay={130}` for additional safety inside scrollables.
128
+ * - Focus state is mirrored on web only (gated setter).
129
+ * - Wrapped in `React.memo`.
45
130
  */
46
131
  function UpiHandle({
47
132
  label = 'Label',
48
- modes: propModes = {},
133
+ modes: propModes = EMPTY_MODES,
49
134
  showIcon = true,
50
135
  iconName = 'ic_scan_qr_code',
51
136
  avatarSource,
52
137
  onPress,
53
138
  onClick,
54
139
  disabled,
55
- accessibilityLabel,
140
+ // accessibilityLabel is accepted on the type for API back-compat; the
141
+ // wrapper renders `accessibilityLabel={undefined}` because the inner Text
142
+ // already carries the label.
143
+ accessibilityLabel: _accessibilityLabel,
56
144
  accessibilityHint,
57
145
  ...rest
58
146
  }: UpiHandleProps) {
59
147
  const { modes: globalModes } = useTokens()
60
- const modes = { ...globalModes, ...propModes }
61
- // Token‑driven container styling
62
- const backgroundColor =
63
- getVariableByName('upiHandle/background', modes) || '#f5f5f5'
64
- const radius = getVariableByName('upiHandle/radius', modes) || 99999
65
- const paddingLeft =
66
- getVariableByName('upiHandle/padding/left', modes) || 4
67
- const paddingRight =
68
- getVariableByName('upiHandle/padding/right', modes) || 14
69
- const paddingVertical =
70
- getVariableByName('upiHandle/padding/vertical', modes) || 3
71
- const gap = getVariableByName('upiHandle/gap', modes) || 6
72
-
73
- // Avatar
74
- const avatarSize =
75
- getVariableByName('upiHandle/image/size', modes) || 23
76
- const avatarRadius =
77
- getVariableByName('upiHandle/image/radius', modes) || 99999
78
-
79
- // Label typography
80
- const labelColor =
81
- getVariableByName('upiHandle/label/color', modes) || '#0d0d0f'
82
- const labelFontSize =
83
- getVariableByName('upiHandle/label/fontSize', modes) || 12
84
- const labelLineHeight =
85
- getVariableByName('upiHandle/label/lineHeight', modes) || 23
86
- const labelFontFamily =
87
- getVariableByName('upiHandle/label/fontFamily', modes) || 'System'
88
- const labelFontWeightRaw =
89
- getVariableByName('upiHandle/label/fontWeight', modes) || 500
90
- const labelFontWeight =
91
- typeof labelFontWeightRaw === 'number'
92
- ? labelFontWeightRaw.toString()
93
- : labelFontWeightRaw
94
-
95
- // Icon sizing
96
- const iconColor =
97
- getVariableByName('upiHandle/icon/color', modes) || '#0d0d0f'
98
- const iconSize =
99
- getVariableByName('upiHandle/icon/size', modes) || 12
100
-
101
- const containerStyle: ViewStyle = {
102
- flexDirection: 'row',
103
- alignItems: 'center',
104
- justifyContent: 'center',
105
- backgroundColor,
106
- paddingLeft,
107
- paddingRight,
108
- paddingVertical,
109
- borderRadius: radius,
110
- gap,
111
- }
112
148
 
113
- const avatarBaseStyle: ImageStyle = {
114
- width: avatarSize,
115
- height: avatarSize,
116
- borderRadius: avatarRadius,
117
- overflow: 'hidden',
118
- }
149
+ const modes = useMemo(
150
+ () => (globalModes === EMPTY_MODES && propModes === EMPTY_MODES
151
+ ? EMPTY_MODES
152
+ : { ...globalModes, ...propModes }),
153
+ [globalModes, propModes]
154
+ )
119
155
 
120
- const labelBaseStyle: TextStyle = {
121
- color: labelColor,
122
- fontSize: labelFontSize,
123
- lineHeight: labelLineHeight,
124
- fontFamily: labelFontFamily,
125
- fontWeight: labelFontWeight,
126
- }
156
+ const tokens = useMemo(() => resolveUpiHandleTokens(modes), [modes])
127
157
 
128
- const iconPlaceholderStyle = {
129
- width: iconSize,
130
- height: iconSize
131
- }
132
-
133
- // Use provided accessibilityLabel or fall back to label
134
- const defaultAccessibilityLabel = accessibilityLabel || `UPI handle ${label}`
135
- const [isPressed, setIsPressed] = useState(false)
158
+ // Focus is a sustained visible state (web-only). Setter is gated so it
159
+ // never fires on native.
136
160
  const [isFocused, setIsFocused] = useState(false)
137
- const pressedStyle = isPressed ? { transform: [{ scale: 0.98 }] } : null
138
- const focusStyle = isFocused ? { borderWidth: 1, borderColor: '#222' } : null
161
+
162
+ const userHandlersRef = useRef<{
163
+ onPressIn?: (e: any) => void
164
+ onPressOut?: (e: any) => void
165
+ onFocus?: (e: any) => void
166
+ onBlur?: (e: any) => void
167
+ }>({})
168
+ userHandlersRef.current.onPressIn = (rest as any)?.onPressIn
169
+ userHandlersRef.current.onPressOut = (rest as any)?.onPressOut
170
+ userHandlersRef.current.onFocus = (rest as any)?.onFocus
171
+ userHandlersRef.current.onBlur = (rest as any)?.onBlur
172
+
173
+ const handlePressIn = useCallback((e: any) => {
174
+ userHandlersRef.current.onPressIn?.(e)
175
+ }, [])
176
+ const handlePressOut = useCallback((e: any) => {
177
+ userHandlersRef.current.onPressOut?.(e)
178
+ }, [])
179
+ const handleFocus = useCallback((e: any) => {
180
+ if (IS_WEB) setIsFocused(true)
181
+ userHandlersRef.current.onFocus?.(e)
182
+ }, [])
183
+ const handleBlur = useCallback((e: any) => {
184
+ if (IS_WEB) setIsFocused(false)
185
+ userHandlersRef.current.onBlur?.(e)
186
+ }, [])
139
187
 
140
188
  const handlePress = onPress || onClick
141
- const Wrapper: any = (rest as any)?.onPress || handlePress ? Pressable : View
189
+ const isPressable = !!((rest as any)?.onPress || handlePress)
142
190
 
143
- return (
144
- <Wrapper
145
- style={[containerStyle, pressedStyle, focusStyle]}
146
- accessibilityRole="text"
147
- accessibilityLabel={undefined}
148
- {...(accessibilityHint !== undefined ? { accessibilityHint } : {})}
149
- onPress={handlePress}
150
- disabled={(rest as any)?.disabled ?? disabled}
151
- onPressIn={(e: any) => {
152
- setIsPressed(true)
153
- ; (rest as any)?.onPressIn?.(e)
154
- }}
155
- onPressOut={(e: any) => {
156
- setIsPressed(false)
157
- ; (rest as any)?.onPressOut?.(e)
158
- }}
159
- onFocus={(e: any) => {
160
- setIsFocused(true)
161
- ; (rest as any)?.onFocus?.(e)
162
- }}
163
- onBlur={(e: any) => {
164
- setIsFocused(false)
165
- ; (rest as any)?.onBlur?.(e)
166
- }}
167
- {...rest}
168
- >
191
+ const pressableStyle = useCallback(
192
+ ({ pressed }: PressableStateCallbackType): StyleProp<ViewStyle> => [
193
+ tokens.containerStyle,
194
+ pressed ? pressedOverlayStyle : null,
195
+ isFocused ? focusOverlayStyle : null,
196
+ ],
197
+ [tokens.containerStyle, isFocused]
198
+ )
199
+
200
+ const staticContainerStyle = useMemo<StyleProp<ViewStyle>>(
201
+ () => [tokens.containerStyle, isFocused ? focusOverlayStyle : null],
202
+ [tokens.containerStyle, isFocused]
203
+ )
204
+
205
+ const innerContent = (
206
+ <>
169
207
  <Image
170
208
  source={avatarSource || DEFAULT_AVATAR_IMAGE}
171
- style={avatarBaseStyle}
209
+ style={tokens.avatarStyle}
172
210
  resizeMode="cover"
173
211
  accessibilityElementsHidden={true}
174
212
  importantForAccessibility="no"
175
213
  />
176
214
  <Text
177
- style={labelBaseStyle}
215
+ style={tokens.labelStyle}
178
216
  numberOfLines={1}
179
217
  ellipsizeMode="tail"
180
218
  accessibilityElementsHidden={true}
@@ -185,18 +223,48 @@ function UpiHandle({
185
223
  {showIcon && (
186
224
  <Icon
187
225
  name={iconName}
188
- size={iconSize}
189
- color={iconColor}
190
- style={iconPlaceholderStyle}
226
+ size={tokens.iconSize}
227
+ color={tokens.iconColor}
228
+ style={tokens.iconPlaceholderStyle}
191
229
  accessibilityElementsHidden={true}
192
230
  importantForAccessibility="no"
193
231
  />
194
232
  )}
195
- </Wrapper>
233
+ </>
196
234
  )
197
- }
198
-
199
- export default UpiHandle
200
235
 
236
+ if (isPressable) {
237
+ return (
238
+ <Pressable
239
+ style={pressableStyle}
240
+ accessibilityRole="text"
241
+ accessibilityLabel={undefined}
242
+ {...(accessibilityHint !== undefined ? { accessibilityHint } : {})}
243
+ onPress={handlePress}
244
+ disabled={(rest as any)?.disabled ?? disabled}
245
+ onPressIn={handlePressIn}
246
+ onPressOut={handlePressOut}
247
+ onFocus={handleFocus}
248
+ onBlur={handleBlur}
249
+ unstable_pressDelay={PRESS_DELAY}
250
+ {...rest}
251
+ >
252
+ {innerContent}
253
+ </Pressable>
254
+ )
255
+ }
201
256
 
257
+ return (
258
+ <View
259
+ style={staticContainerStyle}
260
+ accessibilityRole="text"
261
+ accessibilityLabel={undefined}
262
+ {...(accessibilityHint !== undefined ? { accessibilityHint } : {})}
263
+ {...rest}
264
+ >
265
+ {innerContent}
266
+ </View>
267
+ )
268
+ }
202
269
 
270
+ export default React.memo(UpiHandle)
@@ -2,7 +2,7 @@ import React from 'react'
2
2
  import { View, StyleProp, ViewStyle, ViewProps } from 'react-native'
3
3
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
4
4
  import { useTokens } from '../../design-tokens/JFSThemeProvider'
5
- import { cloneChildrenWithModes } from '../../utils/react-utils'
5
+ import { cloneChildrenWithModes, EMPTY_MODES } from '../../utils/react-utils'
6
6
 
7
7
  export interface VStackProps extends ViewProps {
8
8
  /**
@@ -48,7 +48,7 @@ export const VStack = ({
48
48
  wrap,
49
49
  reverse = false,
50
50
  as,
51
- modes: propModes = {},
51
+ modes: propModes = EMPTY_MODES,
52
52
  style,
53
53
  ...rest
54
54
  }: VStackProps) => {
@@ -190,14 +190,32 @@ function resolveVariable(variableId, modesByCollectionName = {}) {
190
190
  return value;
191
191
  }
192
192
 
193
+ // Per-object serialization cache. Most callers pass the same `modes` object
194
+ // many times in a single render (e.g. one Button does ~21 token lookups with
195
+ // the same `modes`); with a stable identity from the caller (see
196
+ // `EMPTY_MODES` in `utils/react-utils.ts`) this collapses to a single
197
+ // sort+join per modes object across the whole app.
198
+ const serializedModesCache = new WeakMap();
199
+
193
200
  // Serialize modes object to create a stable cache key
194
201
  function serializeModes(modes) {
195
- if (!modes || Object.keys(modes).length === 0) {
202
+ if (!modes || typeof modes !== 'object') {
203
+ return '';
204
+ }
205
+ const cached = serializedModesCache.get(modes);
206
+ if (cached !== undefined) {
207
+ return cached;
208
+ }
209
+ const keys = Object.keys(modes);
210
+ if (keys.length === 0) {
211
+ serializedModesCache.set(modes, '');
196
212
  return '';
197
213
  }
198
214
  // Sort keys for consistent serialization
199
- const sortedKeys = Object.keys(modes).sort();
200
- return sortedKeys.map(key => `${key}:${modes[key]}`).join('|');
215
+ keys.sort();
216
+ const result = keys.map(key => `${key}:${modes[key]}`).join('|');
217
+ serializedModesCache.set(modes, result);
218
+ return result;
201
219
  }
202
220
 
203
221
  // Get variable by name with dynamic mode resolution (optimized with O(1) lookup)
@@ -4,7 +4,7 @@
4
4
  * Auto-generated from SVG files in src/icons/
5
5
  * DO NOT EDIT MANUALLY - Run "npm run icons:generate" to regenerate
6
6
  *
7
- * Generated: 2026-04-15T11:47:37.104Z
7
+ * Generated: 2026-04-20T20:41:14.535Z
8
8
  */
9
9
 
10
10
  // Icon name to SVG data mapping
@@ -1,5 +1,21 @@
1
1
  import React from 'react';
2
2
 
3
+ /**
4
+ * A shared, frozen empty modes object.
5
+ *
6
+ * Components that accept a `modes` prop should use this as the default value
7
+ * instead of the inline `{}` literal. The literal allocates a new object on
8
+ * every render, which:
9
+ * 1. Forces `React.memo` shallow comparisons to fail.
10
+ * 2. Forces `useMemo`/`useEffect` deps that include `modes` to re-run.
11
+ * 3. Forces the design-token resolver to re-serialize the modes object on
12
+ * every call (see `serializeModes` in `figma-variables-resolver.ts`).
13
+ *
14
+ * Sharing a single frozen object across the tree makes all of those checks
15
+ * O(1) identity comparisons in the common "no modes provided" path.
16
+ */
17
+ export const EMPTY_MODES: Readonly<Record<string, any>> = Object.freeze({});
18
+
3
19
  /**
4
20
  * Helper function to recursively clone children and pass modes prop to components that accept it.
5
21
  * This ensures that all child components in slots receive the modes prop from the parent.
@@ -1,2 +0,0 @@
1
- export default function App(): import("react/jsx-runtime").JSX.Element;
2
- //# sourceMappingURL=App.d.ts.map
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=index.d.ts.map
@@ -1,78 +0,0 @@
1
- export = config;
2
- declare const config: import("expo/metro-config").MetroConfig & {
3
- reporter: {
4
- update(): void;
5
- };
6
- watchFolders: string[];
7
- resolver: {
8
- unstable_conditionsByPlatform: {
9
- ios: string[];
10
- android: string[];
11
- web: string[];
12
- };
13
- resolverMainFields: string[];
14
- platforms: string[];
15
- assetExts: string[];
16
- sourceExts: string[];
17
- nodeModulesPaths: string[];
18
- blockList: RegExp[];
19
- };
20
- cacheStores: import("@expo/metro-config/build/file-store").FileStore<any>[];
21
- watcher: {
22
- additionalExts: string[];
23
- };
24
- serializer: {
25
- isThirdPartyModule(module: {
26
- readonly path: string;
27
- }): boolean;
28
- createModuleIdFactory: () => (path: string, context?: {
29
- platform: string;
30
- environment?: string;
31
- }) => number;
32
- getModulesRunBeforeMainModule: () => string[];
33
- getPolyfills: ({ platform }: {
34
- platform?: null | string;
35
- }) => any;
36
- };
37
- server: {
38
- rewriteRequestUrl: (url: string) => string;
39
- port: number;
40
- unstable_serverRoot: string;
41
- };
42
- symbolicator: {
43
- customizeFrame: ($$PARAM_0$$: {
44
- readonly file?: null | string;
45
- readonly lineNumber?: null | number;
46
- readonly column?: null | number;
47
- readonly methodName?: null | string;
48
- }) => (null | undefined | {
49
- readonly collapse?: boolean;
50
- }) | Promise<null | undefined | {
51
- readonly collapse?: boolean;
52
- }>;
53
- };
54
- transformerPath: string;
55
- transformer: {
56
- unstable_renameRequire: false;
57
- _expoRouterPath: string | undefined;
58
- postcssHash: string | null;
59
- browserslistHash: string | null;
60
- sassVersion: string | null;
61
- reanimatedVersion: string | null;
62
- workletsVersion: string | null;
63
- _expoRelativeProjectRoot: string;
64
- unstable_allowRequireContext: true;
65
- allowOptionalDependencies: true;
66
- babelTransformerPath: string;
67
- asyncRequireModulePath: string;
68
- assetRegistryPath: string;
69
- enableBabelRuntime: string | undefined;
70
- getTransformOptions: () => Promise<{
71
- transform: {
72
- experimentalImportSupport: true;
73
- inlineRequires: false;
74
- };
75
- }>;
76
- };
77
- };
78
- //# sourceMappingURL=metro.config.d.ts.map
@@ -1,4 +0,0 @@
1
- export namespace dependency {
2
- let assets: string[];
3
- }
4
- //# sourceMappingURL=react-native.config.d.ts.map