jfs-components 0.0.61 → 0.0.63

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