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,158 +1,163 @@
1
1
  "use strict";
2
2
 
3
- import React, { useState } from 'react';
4
- import { Pressable, View, Text, Image } from 'react-native';
3
+ import React, { useCallback, useMemo, useRef, useState } from 'react';
4
+ import { Pressable, View, Text, Image, Platform } from 'react-native';
5
5
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
6
6
  import { useTokens } from '../../design-tokens/JFSThemeProvider';
7
+ import { EMPTY_MODES } from '../../utils/react-utils';
7
8
  import Icon from '../../icons/Icon';
8
9
 
9
10
  // Default static asset from the component folder.
10
11
  // Consumers can override the image via the `avatarSource` prop if needed.
11
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
12
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
12
13
  const DEFAULT_AVATAR_IMAGE = require('./Image.png');
14
+ const IS_WEB = Platform.OS === 'web';
15
+ const IS_IOS = Platform.OS === 'ios';
16
+ const PRESS_DELAY = IS_IOS ? 130 : 0;
17
+ const pressedOverlayStyle = {
18
+ transform: [{
19
+ scale: 0.98
20
+ }]
21
+ };
22
+ const focusOverlayStyle = {
23
+ borderWidth: 1,
24
+ borderColor: '#222'
25
+ };
26
+ function resolveUpiHandleTokens(modes) {
27
+ const backgroundColor = getVariableByName('upiHandle/background', modes) || '#f5f5f5';
28
+ const radius = getVariableByName('upiHandle/radius', modes) || 99999;
29
+ const paddingLeft = getVariableByName('upiHandle/padding/left', modes) || 4;
30
+ const paddingRight = getVariableByName('upiHandle/padding/right', modes) || 14;
31
+ const paddingVertical = getVariableByName('upiHandle/padding/vertical', modes) || 3;
32
+ const gap = getVariableByName('upiHandle/gap', modes) || 6;
33
+ const avatarSize = getVariableByName('upiHandle/image/size', modes) || 23;
34
+ const avatarRadius = getVariableByName('upiHandle/image/radius', modes) || 99999;
35
+ const labelColor = getVariableByName('upiHandle/label/color', modes) || '#0d0d0f';
36
+ const labelFontSize = getVariableByName('upiHandle/label/fontSize', modes) || 12;
37
+ const labelLineHeight = getVariableByName('upiHandle/label/lineHeight', modes) || 23;
38
+ const labelFontFamily = getVariableByName('upiHandle/label/fontFamily', modes) || 'System';
39
+ const labelFontWeightRaw = getVariableByName('upiHandle/label/fontWeight', modes) || 500;
40
+ const labelFontWeight = typeof labelFontWeightRaw === 'number' ? labelFontWeightRaw.toString() : labelFontWeightRaw;
41
+ const iconColor = getVariableByName('upiHandle/icon/color', modes) || '#0d0d0f';
42
+ const iconSize = getVariableByName('upiHandle/icon/size', modes) || 12;
43
+ return {
44
+ containerStyle: {
45
+ flexDirection: 'row',
46
+ alignItems: 'center',
47
+ justifyContent: 'center',
48
+ backgroundColor,
49
+ paddingLeft,
50
+ paddingRight,
51
+ paddingVertical,
52
+ borderRadius: radius,
53
+ gap
54
+ },
55
+ avatarStyle: {
56
+ width: avatarSize,
57
+ height: avatarSize,
58
+ borderRadius: avatarRadius,
59
+ overflow: 'hidden'
60
+ },
61
+ labelStyle: {
62
+ color: labelColor,
63
+ fontSize: labelFontSize,
64
+ lineHeight: labelLineHeight,
65
+ fontFamily: labelFontFamily,
66
+ fontWeight: labelFontWeight
67
+ },
68
+ iconColor,
69
+ iconSize,
70
+ iconPlaceholderStyle: {
71
+ width: iconSize,
72
+ height: iconSize
73
+ }
74
+ };
75
+ }
76
+
13
77
  /**
14
78
  * UpiHandle pill that mirrors the Figma "UPI Handle" component.
15
79
  *
16
- * Layout:
17
- * - Circular image/avatar on the left
18
- * - Label text in the center
19
- * - Optional QR-code style icon on the right
20
- *
21
- * All visual styling is resolved from Figma variables via `getVariableByName`
22
- * using a `modes` configuration object, matching the rest of this library.
23
- *
24
80
  * @component
25
81
  * @param {Object} props
26
82
  * @param {string} [props.label="Label"] - UPI handle text to display.
27
83
  * @param {Object} [props.modes={}] - Modes object passed directly to `getVariableByName`.
28
84
  * @param {boolean} [props.showIcon=true] - Toggles the trailing icon visibility.
29
- * @param {string} [props.iconName='ic_scan_qr_code'] - Icon name from the actions set (e.g. 'ic_qr_code', 'ic_scan_qr_code').
85
+ * @param {string} [props.iconName='ic_scan_qr_code'] - Icon name from the actions set.
30
86
  * @param {ImageSourcePropType} [props.avatarSource] - Optional custom image source for the avatar.
31
87
  * @param {Function} [props.onClick] - Click/tap handler. Works as an alias for `onPress`.
32
- * @param {string} [props.accessibilityLabel] - Accessibility label for screen readers. If not provided, uses label
88
+ * @param {string} [props.accessibilityLabel] - Accessibility label for screen readers
33
89
  * @param {string} [props.accessibilityHint] - Additional accessibility hint for screen readers
90
+ *
91
+ * Performance notes:
92
+ * - Token reads collapsed into a single `useMemo([modes])`.
93
+ * - Press visual goes through Pressable's `({ pressed })` style callback so
94
+ * a scroll-cancelled touch never schedules a React render. iOS gets
95
+ * `unstable_pressDelay={130}` for additional safety inside scrollables.
96
+ * - Focus state is mirrored on web only (gated setter).
97
+ * - Wrapped in `React.memo`.
34
98
  */
35
99
  function UpiHandle({
36
100
  label = 'Label',
37
- modes: propModes = {},
101
+ modes: propModes = EMPTY_MODES,
38
102
  showIcon = true,
39
103
  iconName = 'ic_scan_qr_code',
40
104
  avatarSource,
41
105
  onPress,
42
106
  onClick,
43
107
  disabled,
44
- accessibilityLabel,
108
+ // accessibilityLabel is accepted on the type for API back-compat; the
109
+ // wrapper renders `accessibilityLabel={undefined}` because the inner Text
110
+ // already carries the label.
111
+ accessibilityLabel: _accessibilityLabel,
45
112
  accessibilityHint,
46
113
  ...rest
47
114
  }) {
48
115
  const {
49
116
  modes: globalModes
50
117
  } = useTokens();
51
- const modes = {
118
+ const modes = useMemo(() => globalModes === EMPTY_MODES && propModes === EMPTY_MODES ? EMPTY_MODES : {
52
119
  ...globalModes,
53
120
  ...propModes
54
- };
55
- // Token‑driven container styling
56
- const backgroundColor = getVariableByName('upiHandle/background', modes) || '#f5f5f5';
57
- const radius = getVariableByName('upiHandle/radius', modes) || 99999;
58
- const paddingLeft = getVariableByName('upiHandle/padding/left', modes) || 4;
59
- const paddingRight = getVariableByName('upiHandle/padding/right', modes) || 14;
60
- const paddingVertical = getVariableByName('upiHandle/padding/vertical', modes) || 3;
61
- const gap = getVariableByName('upiHandle/gap', modes) || 6;
62
-
63
- // Avatar
64
- const avatarSize = getVariableByName('upiHandle/image/size', modes) || 23;
65
- const avatarRadius = getVariableByName('upiHandle/image/radius', modes) || 99999;
66
-
67
- // Label typography
68
- const labelColor = getVariableByName('upiHandle/label/color', modes) || '#0d0d0f';
69
- const labelFontSize = getVariableByName('upiHandle/label/fontSize', modes) || 12;
70
- const labelLineHeight = getVariableByName('upiHandle/label/lineHeight', modes) || 23;
71
- const labelFontFamily = getVariableByName('upiHandle/label/fontFamily', modes) || 'System';
72
- const labelFontWeightRaw = getVariableByName('upiHandle/label/fontWeight', modes) || 500;
73
- const labelFontWeight = typeof labelFontWeightRaw === 'number' ? labelFontWeightRaw.toString() : labelFontWeightRaw;
121
+ }, [globalModes, propModes]);
122
+ const tokens = useMemo(() => resolveUpiHandleTokens(modes), [modes]);
74
123
 
75
- // Icon sizing
76
- const iconColor = getVariableByName('upiHandle/icon/color', modes) || '#0d0d0f';
77
- const iconSize = getVariableByName('upiHandle/icon/size', modes) || 12;
78
- const containerStyle = {
79
- flexDirection: 'row',
80
- alignItems: 'center',
81
- justifyContent: 'center',
82
- backgroundColor,
83
- paddingLeft,
84
- paddingRight,
85
- paddingVertical,
86
- borderRadius: radius,
87
- gap
88
- };
89
- const avatarBaseStyle = {
90
- width: avatarSize,
91
- height: avatarSize,
92
- borderRadius: avatarRadius,
93
- overflow: 'hidden'
94
- };
95
- const labelBaseStyle = {
96
- color: labelColor,
97
- fontSize: labelFontSize,
98
- lineHeight: labelLineHeight,
99
- fontFamily: labelFontFamily,
100
- fontWeight: labelFontWeight
101
- };
102
- const iconPlaceholderStyle = {
103
- width: iconSize,
104
- height: iconSize
105
- };
106
-
107
- // Use provided accessibilityLabel or fall back to label
108
- const defaultAccessibilityLabel = accessibilityLabel || `UPI handle ${label}`;
109
- const [isPressed, setIsPressed] = useState(false);
124
+ // Focus is a sustained visible state (web-only). Setter is gated so it
125
+ // never fires on native.
110
126
  const [isFocused, setIsFocused] = useState(false);
111
- const pressedStyle = isPressed ? {
112
- transform: [{
113
- scale: 0.98
114
- }]
115
- } : null;
116
- const focusStyle = isFocused ? {
117
- borderWidth: 1,
118
- borderColor: '#222'
119
- } : null;
127
+ const userHandlersRef = useRef({});
128
+ userHandlersRef.current.onPressIn = rest?.onPressIn;
129
+ userHandlersRef.current.onPressOut = rest?.onPressOut;
130
+ userHandlersRef.current.onFocus = rest?.onFocus;
131
+ userHandlersRef.current.onBlur = rest?.onBlur;
132
+ const handlePressIn = useCallback(e => {
133
+ userHandlersRef.current.onPressIn?.(e);
134
+ }, []);
135
+ const handlePressOut = useCallback(e => {
136
+ userHandlersRef.current.onPressOut?.(e);
137
+ }, []);
138
+ const handleFocus = useCallback(e => {
139
+ if (IS_WEB) setIsFocused(true);
140
+ userHandlersRef.current.onFocus?.(e);
141
+ }, []);
142
+ const handleBlur = useCallback(e => {
143
+ if (IS_WEB) setIsFocused(false);
144
+ userHandlersRef.current.onBlur?.(e);
145
+ }, []);
120
146
  const handlePress = onPress || onClick;
121
- const Wrapper = rest?.onPress || handlePress ? Pressable : View;
122
- return /*#__PURE__*/_jsxs(Wrapper, {
123
- style: [containerStyle, pressedStyle, focusStyle],
124
- accessibilityRole: "text",
125
- accessibilityLabel: undefined,
126
- ...(accessibilityHint !== undefined ? {
127
- accessibilityHint
128
- } : {}),
129
- onPress: handlePress,
130
- disabled: rest?.disabled ?? disabled,
131
- onPressIn: e => {
132
- setIsPressed(true);
133
- rest?.onPressIn?.(e);
134
- },
135
- onPressOut: e => {
136
- setIsPressed(false);
137
- rest?.onPressOut?.(e);
138
- },
139
- onFocus: e => {
140
- setIsFocused(true);
141
- rest?.onFocus?.(e);
142
- },
143
- onBlur: e => {
144
- setIsFocused(false);
145
- rest?.onBlur?.(e);
146
- },
147
- ...rest,
147
+ const isPressable = !!(rest?.onPress || handlePress);
148
+ const pressableStyle = useCallback(({
149
+ pressed
150
+ }) => [tokens.containerStyle, pressed ? pressedOverlayStyle : null, isFocused ? focusOverlayStyle : null], [tokens.containerStyle, isFocused]);
151
+ const staticContainerStyle = useMemo(() => [tokens.containerStyle, isFocused ? focusOverlayStyle : null], [tokens.containerStyle, isFocused]);
152
+ const innerContent = /*#__PURE__*/_jsxs(_Fragment, {
148
153
  children: [/*#__PURE__*/_jsx(Image, {
149
154
  source: avatarSource || DEFAULT_AVATAR_IMAGE,
150
- style: avatarBaseStyle,
155
+ style: tokens.avatarStyle,
151
156
  resizeMode: "cover",
152
157
  accessibilityElementsHidden: true,
153
158
  importantForAccessibility: "no"
154
159
  }), /*#__PURE__*/_jsx(Text, {
155
- style: labelBaseStyle,
160
+ style: tokens.labelStyle,
156
161
  numberOfLines: 1,
157
162
  ellipsizeMode: "tail",
158
163
  accessibilityElementsHidden: true,
@@ -160,12 +165,41 @@ function UpiHandle({
160
165
  children: label
161
166
  }), showIcon && /*#__PURE__*/_jsx(Icon, {
162
167
  name: iconName,
163
- size: iconSize,
164
- color: iconColor,
165
- style: iconPlaceholderStyle,
168
+ size: tokens.iconSize,
169
+ color: tokens.iconColor,
170
+ style: tokens.iconPlaceholderStyle,
166
171
  accessibilityElementsHidden: true,
167
172
  importantForAccessibility: "no"
168
173
  })]
169
174
  });
175
+ if (isPressable) {
176
+ return /*#__PURE__*/_jsx(Pressable, {
177
+ style: pressableStyle,
178
+ accessibilityRole: "text",
179
+ accessibilityLabel: undefined,
180
+ ...(accessibilityHint !== undefined ? {
181
+ accessibilityHint
182
+ } : {}),
183
+ onPress: handlePress,
184
+ disabled: rest?.disabled ?? disabled,
185
+ onPressIn: handlePressIn,
186
+ onPressOut: handlePressOut,
187
+ onFocus: handleFocus,
188
+ onBlur: handleBlur,
189
+ unstable_pressDelay: PRESS_DELAY,
190
+ ...rest,
191
+ children: innerContent
192
+ });
193
+ }
194
+ return /*#__PURE__*/_jsx(View, {
195
+ style: staticContainerStyle,
196
+ accessibilityRole: "text",
197
+ accessibilityLabel: undefined,
198
+ ...(accessibilityHint !== undefined ? {
199
+ accessibilityHint
200
+ } : {}),
201
+ ...rest,
202
+ children: innerContent
203
+ });
170
204
  }
171
- export default UpiHandle;
205
+ export default /*#__PURE__*/React.memo(UpiHandle);
@@ -4,7 +4,7 @@ import React from 'react';
4
4
  import { View } from 'react-native';
5
5
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
6
6
  import { useTokens } from '../../design-tokens/JFSThemeProvider';
7
- import { cloneChildrenWithModes } from '../../utils/react-utils';
7
+ import { cloneChildrenWithModes, EMPTY_MODES } from '../../utils/react-utils';
8
8
  import { jsx as _jsx } from "react/jsx-runtime";
9
9
  /**
10
10
  * VStack component for vertical layout using design token spacing.
@@ -16,7 +16,7 @@ export const VStack = ({
16
16
  wrap,
17
17
  reverse = false,
18
18
  as,
19
- modes: propModes = {},
19
+ modes: propModes = EMPTY_MODES,
20
20
  style,
21
21
  ...rest
22
22
  }) => {
@@ -187,14 +187,32 @@ function resolveVariable(variableId, modesByCollectionName = {}) {
187
187
  return value;
188
188
  }
189
189
 
190
+ // Per-object serialization cache. Most callers pass the same `modes` object
191
+ // many times in a single render (e.g. one Button does ~21 token lookups with
192
+ // the same `modes`); with a stable identity from the caller (see
193
+ // `EMPTY_MODES` in `utils/react-utils.ts`) this collapses to a single
194
+ // sort+join per modes object across the whole app.
195
+ const serializedModesCache = new WeakMap();
196
+
190
197
  // Serialize modes object to create a stable cache key
191
198
  function serializeModes(modes) {
192
- if (!modes || Object.keys(modes).length === 0) {
199
+ if (!modes || typeof modes !== 'object') {
200
+ return '';
201
+ }
202
+ const cached = serializedModesCache.get(modes);
203
+ if (cached !== undefined) {
204
+ return cached;
205
+ }
206
+ const keys = Object.keys(modes);
207
+ if (keys.length === 0) {
208
+ serializedModesCache.set(modes, '');
193
209
  return '';
194
210
  }
195
211
  // Sort keys for consistent serialization
196
- const sortedKeys = Object.keys(modes).sort();
197
- return sortedKeys.map(key => `${key}:${modes[key]}`).join('|');
212
+ keys.sort();
213
+ const result = keys.map(key => `${key}:${modes[key]}`).join('|');
214
+ serializedModesCache.set(modes, result);
215
+ return result;
198
216
  }
199
217
 
200
218
  // Get variable by name with dynamic mode resolution (optimized with O(1) lookup)