jfs-components 0.0.62 → 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 (255) 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 +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 +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/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 +2 -1
  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 +271 -159
  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 +2 -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 +2 -1
  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 +323 -167
  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,27 +1,72 @@
1
1
  "use strict";
2
2
 
3
- import React, { useState } from 'react';
4
- import { Pressable, Text, View } from 'react-native';
3
+ import React, { useCallback, useMemo, useRef, useState } from 'react';
4
+ import { Platform, Pressable, Text, 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 Icon from '../../icons/Icon';
8
8
  import { usePressableWebSupport } from '../../utils/web-platform-utils';
9
+ import { EMPTY_MODES } from '../../utils/react-utils';
9
10
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
11
+ const IS_WEB = Platform.OS === 'web';
12
+ const IS_IOS = Platform.OS === 'ios';
13
+ const PRESS_DELAY = IS_IOS ? 130 : 0;
14
+ const pressedOverlayStyle = {
15
+ opacity: 0.7
16
+ };
17
+ const hoverOverlayStyle = {
18
+ opacity: 0.85
19
+ };
20
+ const focusOverlayStyle = {
21
+ borderBottomWidth: 2,
22
+ borderBottomColor: '#222'
23
+ };
24
+ function resolveBottomNavItemTokens(modes, disabled, iconColorOverride, iconSizeOverride) {
25
+ const gap = getVariableByName('bottomNavItem/gap', modes) ?? 6;
26
+ const fontFamily = getVariableByName('bottomNavItem/fontFamily', modes) || 'JioType_Var:Medium';
27
+ const fontWeightRaw = getVariableByName('bottomNavItem/fontWeight', modes) ?? 500;
28
+ const fontWeight = typeof fontWeightRaw === 'number' ? fontWeightRaw.toString() : fontWeightRaw;
29
+ const fontSize = getVariableByName('bottomNavItem/fontSize', modes) ?? 11;
30
+ const lineHeight = getVariableByName('bottomNavItem/lineHeight', modes) ?? 14;
31
+ const labelColor = getVariableByName('bottomNavItem/label/color', modes) || '#0d0d0f';
32
+ const resolvedIconColor = iconColorOverride || getVariableByName('bottomNavItem/icon/color', modes) || '#ad8545';
33
+ const resolvedIconSize = iconSizeOverride ?? getVariableByName('bottomNavItem/icon/size', modes) ?? 24;
34
+ return {
35
+ baseContainerStyle: {
36
+ alignItems: 'center',
37
+ opacity: disabled ? 0.5 : 1
38
+ },
39
+ textStyle: {
40
+ color: labelColor,
41
+ fontFamily,
42
+ fontWeight: fontWeight,
43
+ fontSize,
44
+ lineHeight,
45
+ textAlign: 'center',
46
+ marginTop: gap
47
+ },
48
+ iconColor: resolvedIconColor,
49
+ iconSize: resolvedIconSize
50
+ };
51
+ }
52
+
10
53
  /**
11
54
  * Bottom navigation item with icon and label stacked vertically.
12
55
  *
13
- * All visual attributes resolve from Figma tokens via `getVariableByName`,
14
- * with the `modes` object passed straight through.
15
- *
16
- * @component
17
- * @param {Object} props
18
- * @param {string} [props.iconName="ic_home"] - Icon name from the registry.
19
- * ...
56
+ * Performance notes:
57
+ * - Token reads collapsed into a single `useMemo([modes, disabled, iconColor, iconSize])`.
58
+ * - Press visual via Pressable's `({ pressed })` style callback.
59
+ * - Hover and focus state are mirrored on web only (gated setters).
60
+ * - The previous version had no-op `onPressIn`/`onPressOut` handlers that
61
+ * forwarded to user callbacks but did nothing else they were still
62
+ * creating fresh closures every render and forcing Pressable to re-bind.
63
+ * Now stable via a ref-backed wrapper.
64
+ * - Wrapped in `React.memo`.
20
65
  */
21
66
  function BottomNavItem({
22
67
  iconName = 'ic_home',
23
68
  label = 'Home',
24
- modes: propModes = {},
69
+ modes: propModes = EMPTY_MODES,
25
70
  onPress,
26
71
  disabled = false,
27
72
  style,
@@ -37,54 +82,21 @@ function BottomNavItem({
37
82
  const {
38
83
  modes: globalModes
39
84
  } = useTokens();
40
- const modes = {
85
+ const modes = useMemo(() => globalModes === EMPTY_MODES && propModes === EMPTY_MODES ? EMPTY_MODES : {
41
86
  ...globalModes,
42
87
  ...propModes
43
- };
88
+ }, [globalModes, propModes]);
89
+ const tokens = useMemo(() => resolveBottomNavItemTokens(modes, disabled, iconColorOverride, iconSizeOverride), [modes, disabled, iconColorOverride, iconSizeOverride]);
44
90
  const [isFocused, setIsFocused] = useState(false);
45
91
  const [isHovered, setIsHovered] = useState(false);
46
- const pressedStyle = {
47
- opacity: 0.7
48
- };
49
- const focusStyle = {
50
- borderBottomWidth: 2,
51
- borderBottomColor: '#222'
52
- };
53
- const hoverStyle = {
54
- opacity: 0.85
55
- };
56
- // Resolve spacing and typography
57
- const gap = getVariableByName('bottomNavItem/gap', modes) ?? 6;
58
- const fontFamily = getVariableByName('bottomNavItem/fontFamily', modes) || 'JioType_Var:Medium';
59
- const fontWeightRaw = getVariableByName('bottomNavItem/fontWeight', modes) ?? 500;
60
- const fontWeight = typeof fontWeightRaw === 'number' ? fontWeightRaw.toString() : fontWeightRaw;
61
- const fontSize = getVariableByName('bottomNavItem/fontSize', modes) ?? 11;
62
- const lineHeight = getVariableByName('bottomNavItem/lineHeight', modes) ?? 14;
63
-
64
- // Resolve Label Color
65
- const labelColor = getVariableByName('bottomNavItem/label/color', modes) || '#0d0d0f';
66
-
67
- // Resolve icon appearance
68
- const resolvedIconColor = iconColorOverride || getVariableByName('bottomNavItem/icon/color', modes) || '#ad8545';
69
- const resolvedIconSize = iconSizeOverride ?? getVariableByName('bottomNavItem/icon/size', modes) ?? 24;
70
- const baseContainerStyle = {
71
- alignItems: 'center',
72
- opacity: disabled ? 0.5 : 1
73
- };
74
- const textStyle = {
75
- color: labelColor,
76
- fontFamily,
77
- fontWeight,
78
- fontSize,
79
- lineHeight,
80
- textAlign: 'center',
81
- marginTop: gap
82
- };
83
-
84
- // Use provided accessibilityLabel or fall back to label
92
+ const userHandlersRef = useRef({});
93
+ userHandlersRef.current.onPressIn = rest?.onPressIn;
94
+ userHandlersRef.current.onPressOut = rest?.onPressOut;
95
+ userHandlersRef.current.onFocus = rest?.onFocus;
96
+ userHandlersRef.current.onBlur = rest?.onBlur;
97
+ userHandlersRef.current.onHoverIn = rest?.onHoverIn;
98
+ userHandlersRef.current.onHoverOut = rest?.onHoverOut;
85
99
  const defaultAccessibilityLabel = accessibilityLabel || label;
86
-
87
- // Get web platform support props (only used when onPress is defined)
88
100
  const webProps = usePressableWebSupport({
89
101
  restProps: rest,
90
102
  onPress: disabled ? undefined : onPress,
@@ -92,23 +104,50 @@ function BottomNavItem({
92
104
  accessibilityLabel: defaultAccessibilityLabel,
93
105
  webAccessibilityProps
94
106
  });
95
- const renderContent = () => /*#__PURE__*/_jsxs(_Fragment, {
107
+ const composedTextStyle = useMemo(() => labelStyle ? [tokens.textStyle, labelStyle] : tokens.textStyle, [tokens.textStyle, labelStyle]);
108
+ const renderContent = /*#__PURE__*/_jsxs(_Fragment, {
96
109
  children: [/*#__PURE__*/_jsx(Icon, {
97
110
  name: iconName,
98
- size: resolvedIconSize,
99
- color: resolvedIconColor,
111
+ size: tokens.iconSize,
112
+ color: tokens.iconColor,
100
113
  accessibilityElementsHidden: true,
101
114
  importantForAccessibility: "no"
102
115
  }), /*#__PURE__*/_jsx(Text, {
103
- style: [textStyle, labelStyle],
116
+ style: composedTextStyle,
104
117
  accessibilityElementsHidden: true,
105
118
  importantForAccessibility: "no",
106
119
  children: label
107
120
  })]
108
121
  });
122
+ const staticContainerStyle = useMemo(() => style ? [tokens.baseContainerStyle, style] : tokens.baseContainerStyle, [tokens.baseContainerStyle, style]);
123
+ const handlePressIn = useCallback(e => {
124
+ userHandlersRef.current.onPressIn?.(e);
125
+ }, []);
126
+ const handlePressOut = useCallback(e => {
127
+ userHandlersRef.current.onPressOut?.(e);
128
+ }, []);
129
+ const handleFocus = useCallback(e => {
130
+ if (IS_WEB) setIsFocused(true);
131
+ userHandlersRef.current.onFocus?.(e);
132
+ }, []);
133
+ const handleBlur = useCallback(e => {
134
+ if (IS_WEB) setIsFocused(false);
135
+ userHandlersRef.current.onBlur?.(e);
136
+ }, []);
137
+ const handleHoverIn = useCallback(e => {
138
+ if (IS_WEB) setIsHovered(true);
139
+ userHandlersRef.current.onHoverIn?.(e);
140
+ }, []);
141
+ const handleHoverOut = useCallback(e => {
142
+ if (IS_WEB) setIsHovered(false);
143
+ userHandlersRef.current.onHoverOut?.(e);
144
+ }, []);
145
+ const pressableStyle = useCallback(({
146
+ pressed
147
+ }) => [tokens.baseContainerStyle, style, pressed && !disabled ? pressedOverlayStyle : null, isHovered && !disabled ? hoverOverlayStyle : null, isFocused && !disabled ? focusOverlayStyle : null], [tokens.baseContainerStyle, style, isHovered, isFocused, disabled]);
109
148
  if (!onPress) {
110
149
  return /*#__PURE__*/_jsx(View, {
111
- style: [baseContainerStyle, style],
150
+ style: staticContainerStyle,
112
151
  accessibilityRole: "tab",
113
152
  accessibilityLabel: undefined,
114
153
  accessibilityHint: accessibilityHint,
@@ -117,7 +156,7 @@ function BottomNavItem({
117
156
  ...accessibilityState
118
157
  },
119
158
  ...rest,
120
- children: renderContent()
159
+ children: renderContent
121
160
  });
122
161
  }
123
162
  return /*#__PURE__*/_jsx(Pressable, {
@@ -131,35 +170,16 @@ function BottomNavItem({
131
170
  },
132
171
  onPress: onPress,
133
172
  disabled: disabled,
134
- onPressIn: e => {
135
- ;
136
- rest?.onPressIn?.(e);
137
- },
138
- onPressOut: e => {
139
- ;
140
- rest?.onPressOut?.(e);
141
- },
142
- onFocus: e => {
143
- setIsFocused(true);
144
- rest?.onFocus?.(e);
145
- },
146
- onBlur: e => {
147
- setIsFocused(false);
148
- rest?.onBlur?.(e);
149
- },
150
- onHoverIn: e => {
151
- setIsHovered(true);
152
- rest?.onHoverIn?.(e);
153
- },
154
- onHoverOut: e => {
155
- setIsHovered(false);
156
- rest?.onHoverOut?.(e);
157
- },
158
- style: ({
159
- pressed
160
- }) => [baseContainerStyle, style, pressed && !disabled ? pressedStyle : null, isHovered && !disabled ? hoverStyle : null, isFocused && !disabled ? focusStyle : null],
173
+ onPressIn: handlePressIn,
174
+ onPressOut: handlePressOut,
175
+ onFocus: handleFocus,
176
+ onBlur: handleBlur,
177
+ onHoverIn: handleHoverIn,
178
+ onHoverOut: handleHoverOut,
179
+ unstable_pressDelay: PRESS_DELAY,
180
+ style: pressableStyle,
161
181
  ...webProps,
162
- children: renderContent()
182
+ children: renderContent
163
183
  });
164
184
  }
165
- export default BottomNavItem;
185
+ export default /*#__PURE__*/React.memo(BottomNavItem);
@@ -1,11 +1,121 @@
1
1
  "use strict";
2
2
 
3
- import React, { useMemo, useState } from 'react';
4
- import { Pressable, Text, View } from 'react-native';
3
+ import React, { useCallback, useMemo, useRef, useState } from 'react';
4
+ import { Platform, Pressable, Text, View } from 'react-native';
5
5
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
6
6
  import { usePressableWebSupport } from '../../utils/web-platform-utils';
7
+ import { EMPTY_MODES } from '../../utils/react-utils';
7
8
  import Icon from '../../icons/Icon';
8
9
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
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
+
17
+ // iOS Pressable inside a ScrollView fires onPressIn on touchstart by default,
18
+ // which causes a visible "press" flash that snaps back when ScrollView claims
19
+ // the gesture. Delaying press by ~130ms lets the scroll responder cancel the
20
+ // gesture before the pressed visual is ever applied.
21
+ //
22
+ // Android uses the responder system + the touchable cancellation pipeline and
23
+ // does not need an extra delay; setting it to 0 keeps tap latency identical
24
+ // to today's behavior on Android.
25
+ const PRESS_DELAY = IS_IOS ? 130 : 0;
26
+ const containerLayoutStyle = {
27
+ flexDirection: 'row',
28
+ alignItems: 'center',
29
+ justifyContent: 'center'
30
+ };
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // Token resolution
34
+ // ---------------------------------------------------------------------------
35
+ //
36
+ // Memoizing all token reads in a single useMemo keyed on (modes, disabled)
37
+ // turns the previous ~21 cache lookups + arithmetic per render into a single
38
+ // resolve per (modes, disabled) tuple. With `EMPTY_MODES` as the default,
39
+ // the most common path (no modes prop) is resolved once for the whole app.
40
+ // ---------------------------------------------------------------------------
41
+
42
+ function resolveButtonTokens(modes, disabled) {
43
+ const backgroundColor = getVariableByName('button/background', modes) || '#cfa159';
44
+ const borderColor = getVariableByName('button/border/color', modes) || 'rgba(255,255,255,0)';
45
+ const borderWidth = getVariableByName('button/border/size', modes);
46
+ const radius = getVariableByName('button/radius', modes) || 9999;
47
+ const paddingHorizontal = getVariableByName('button/padding/horizontal', modes) || 20;
48
+ const paddingVertical = getVariableByName('button/padding/vertical', modes) || 12;
49
+ const gap = getVariableByName('button/gap', modes) || 8;
50
+ const fontFamily = getVariableByName('button/fontFamily', modes) || 'System';
51
+ const fontWeightValue = getVariableByName('button/fontWeight', modes) || 700;
52
+ const fontWeight = typeof fontWeightValue === 'number' ? fontWeightValue.toString() : fontWeightValue;
53
+ const lineHeight = getVariableByName('button/lineHeight', modes) || 19;
54
+ const fontSize = getVariableByName('button/fontSize', modes) || 16;
55
+ const textColor = getVariableByName('button/foreground', modes) || '#0f0d0a';
56
+ const iconSize = getVariableByName('button/icon/size', modes) ?? 18;
57
+
58
+ // Pressed tokens are always resolved so the Pressable `style` callback can
59
+ // apply the pressed visual without scheduling a React render.
60
+ const pressedModes = {
61
+ ...modes,
62
+ 'Button / State': 'Pressed'
63
+ };
64
+ const pressedBg = getVariableByName('button/background', pressedModes) || backgroundColor;
65
+ const pressedBorderColor = getVariableByName('button/border/color', pressedModes) || borderColor;
66
+ const pressedTextColor = getVariableByName('button/foreground', pressedModes) || textColor;
67
+
68
+ // Hover tokens are only meaningful on web. Skipping them on native saves
69
+ // ~3 token lookups + an object spread per render on every Button.
70
+ let hoverContainer = null;
71
+ let hoverTextColor = null;
72
+ if (IS_WEB) {
73
+ const hoverModes = {
74
+ ...modes,
75
+ 'Button / State': 'Hover'
76
+ };
77
+ const hoverBg = getVariableByName('button/background', hoverModes) || backgroundColor;
78
+ const hoverBorderColor = getVariableByName('button/border/color', hoverModes) || borderColor;
79
+ hoverTextColor = getVariableByName('button/foreground', hoverModes) || textColor;
80
+ hoverContainer = {
81
+ backgroundColor: hoverBg,
82
+ borderColor: hoverBorderColor
83
+ };
84
+ }
85
+ return {
86
+ container: {
87
+ ...containerLayoutStyle,
88
+ paddingHorizontal: paddingHorizontal,
89
+ paddingVertical: paddingVertical,
90
+ borderRadius: radius,
91
+ borderWidth: borderWidth ?? 1,
92
+ borderColor: borderColor,
93
+ backgroundColor: backgroundColor,
94
+ gap: gap,
95
+ opacity: disabled ? 0.5 : 1
96
+ },
97
+ baseLabel: {
98
+ color: textColor,
99
+ fontFamily: fontFamily,
100
+ fontWeight: fontWeight,
101
+ fontSize: fontSize,
102
+ lineHeight: lineHeight
103
+ },
104
+ hoverContainer,
105
+ pressedContainer: {
106
+ backgroundColor: pressedBg,
107
+ borderColor: pressedBorderColor
108
+ },
109
+ hoverTextColor,
110
+ pressedTextColor,
111
+ baseIconColor: textColor,
112
+ hoverIconColor: hoverTextColor,
113
+ pressedIconColor: pressedTextColor,
114
+ iconSize,
115
+ accessoryOffset: gap / 2
116
+ };
117
+ }
118
+
9
119
  /**
10
120
  * Button component that maps directly to the Figma design using design tokens.
11
121
  *
@@ -24,15 +134,31 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
24
134
  * @param {string} [props.accessibilityLabel] - Accessibility label for screen readers. If not provided, uses label or children text
25
135
  * @param {string} [props.accessibilityHint] - Additional accessibility hint for screen readers
26
136
  * @param {Object} [props.accessibilityState] - Additional accessibility state information
137
+ *
138
+ * Performance notes:
139
+ * - The pressed visual (background/border swap) is applied through the
140
+ * Pressable `style` callback, NOT a mirrored React state. This avoids the
141
+ * flicker that scrolling parents previously caused: when a ScrollView
142
+ * cancels a touch the host view simply reverts its style without React
143
+ * rendering. On iOS we additionally use `unstable_pressDelay` so the
144
+ * pressed visual is never even applied during a scroll-cancelled touch.
145
+ * - Hover state is only mirrored into React state on web (where hover events
146
+ * fire). On native the setter call is gated out so the component never
147
+ * re-renders due to hover.
148
+ * - All design-token reads are folded into a single `useMemo` keyed on
149
+ * `(modes, disabled)`.
150
+ * - The component is wrapped in `React.memo`. Callers that want to take
151
+ * advantage of this should pass stable `modes` (the default `EMPTY_MODES`
152
+ * is already stable) and stable callback props.
27
153
  */
28
- function Button({
154
+ function ButtonImpl({
29
155
  label = 'Button',
30
156
  children,
31
157
  renderContent,
32
158
  leading,
33
159
  trailing,
34
160
  icon,
35
- modes = {},
161
+ modes = EMPTY_MODES,
36
162
  onPress,
37
163
  disabled = false,
38
164
  style,
@@ -43,85 +169,50 @@ function Button({
43
169
  webAccessibilityProps,
44
170
  ...rest
45
171
  }) {
46
- // Resolve design tokens with the provided modes
47
- const backgroundColor = getVariableByName('button/background', modes) || '#cfa159';
48
- const borderColor = getVariableByName('button/border/color', modes) || 'rgba(255,255,255,0)';
49
- const borderWidth = getVariableByName('button/border/size', modes);
50
- const radius = getVariableByName('button/radius', modes) || 9999;
51
- const paddingHorizontal = getVariableByName('button/padding/horizontal', modes) || 20;
52
- const paddingVertical = getVariableByName('button/padding/vertical', modes) || 12;
53
- const gap = getVariableByName('button/gap', modes) || 8;
54
- const fontFamily = getVariableByName('button/fontFamily', modes) || 'System';
55
- const fontWeightValue = getVariableByName('button/fontWeight', modes) || 700;
56
- const fontWeight = typeof fontWeightValue === 'number' ? fontWeightValue.toString() : fontWeightValue;
57
- const lineHeight = getVariableByName('button/lineHeight', modes) || 19;
58
- const fontSize = getVariableByName('button/fontSize', modes) || 16;
59
- const textColor = getVariableByName('button/foreground', modes) || '#0f0d0a';
60
- const iconColor = textColor;
61
- const iconSize = getVariableByName('button/icon/size', modes) ?? 18;
172
+ // Hover state is web-only in practice; the setter is gated so native taps
173
+ // never schedule a re-render through this hook.
62
174
  const [isHovered, setIsHovered] = useState(false);
63
- const [isPressed, setIsPressed] = useState(false);
64
- const hoverModes = {
65
- ...modes,
66
- "Button / State": "Hover"
67
- };
68
- const hoverBg = getVariableByName('button/background', hoverModes) || backgroundColor;
69
- const hoverBorderColor = getVariableByName('button/border/color', hoverModes) || borderColor;
70
- const hoverTextColor = getVariableByName('button/foreground', hoverModes) || textColor;
71
- const hoverIconColor = hoverTextColor;
72
- const pressedModes = {
73
- ...modes,
74
- "Button / State": "Pressed"
75
- };
76
- const pressedBg = getVariableByName('button/background', pressedModes) || backgroundColor;
77
- const pressedBorderColor = getVariableByName('button/border/color', pressedModes) || borderColor;
78
- const pressedTextColor = getVariableByName('button/foreground', pressedModes) || textColor;
79
- const pressedIconColor = pressedTextColor;
80
- const activeTextColor = isPressed && !disabled ? pressedTextColor : isHovered && !disabled ? hoverTextColor : textColor;
81
- const activeIconColor = isPressed && !disabled ? pressedIconColor : isHovered && !disabled ? hoverIconColor : iconColor;
82
- const baseLabelTextStyle = {
83
- color: activeTextColor,
84
- fontFamily,
85
- fontWeight,
86
- fontSize,
87
- lineHeight
88
- };
89
- const mergedLabelTextStyle = [baseLabelTextStyle, labelStyle];
175
+
176
+ // Mirror user-supplied handlers in a ref so our wrapper handlers can stay
177
+ // referentially stable (otherwise Pressable would re-bind every render).
178
+ const userHandlersRef = useRef({});
179
+ userHandlersRef.current.onPressIn = rest?.onPressIn;
180
+ userHandlersRef.current.onPressOut = rest?.onPressOut;
181
+ userHandlersRef.current.onHoverIn = rest?.onHoverIn;
182
+ userHandlersRef.current.onHoverOut = rest?.onHoverOut;
183
+ const tokens = useMemo(() => resolveButtonTokens(modes, disabled), [modes, disabled]);
184
+
185
+ // Active label color: base by default; hover override (web-only) when hovered.
186
+ // Press color is intentionally NOT applied to the label on native — applying
187
+ // it would require a React render per touch and re-introduce the flicker.
188
+ const activeLabelStyle = useMemo(() => {
189
+ const hoverOverride = isHovered && !disabled && tokens.hoverTextColor ? {
190
+ color: tokens.hoverTextColor
191
+ } : null;
192
+ return [tokens.baseLabel, hoverOverride, labelStyle];
193
+ }, [tokens.baseLabel, tokens.hoverTextColor, isHovered, disabled, labelStyle]);
194
+ const activeIconColor = isHovered && !disabled && tokens.hoverIconColor ? tokens.hoverIconColor : tokens.baseIconColor;
90
195
  let content;
91
196
  if (renderContent) {
92
197
  // Preferred extension point: caller fully controls the inner node but we
93
198
  // still provide the token-derived text styles.
94
- content = renderContent(mergedLabelTextStyle);
199
+ content = renderContent(activeLabelStyle);
95
200
  } else if (children !== undefined && children !== null) {
96
201
  // Full-content override: label and labelStyle are intentionally ignored.
97
202
  content = children;
98
203
  } else {
99
204
  content = /*#__PURE__*/_jsx(Text, {
100
- style: mergedLabelTextStyle,
205
+ style: activeLabelStyle,
101
206
  numberOfLines: 1,
102
207
  children: label
103
208
  });
104
209
  }
105
- const containerBaseStyle = {
106
- flexDirection: 'row',
107
- alignItems: 'center',
108
- justifyContent: 'center',
109
- paddingHorizontal,
110
- paddingVertical,
111
- borderRadius: radius,
112
- borderWidth: borderWidth ?? 1,
113
- borderColor,
114
- backgroundColor,
115
- gap,
116
- opacity: disabled ? 0.5 : 1
117
- };
118
- const accessoryOffset = gap / 2;
119
210
  const leadingAccessoryStyle = useMemo(() => ({
120
- marginRight: accessoryOffset
121
- }), [accessoryOffset]);
211
+ marginRight: tokens.accessoryOffset
212
+ }), [tokens.accessoryOffset]);
122
213
  const trailingAccessoryStyle = useMemo(() => ({
123
- marginLeft: accessoryOffset
124
- }), [accessoryOffset]);
214
+ marginLeft: tokens.accessoryOffset
215
+ }), [tokens.accessoryOffset]);
125
216
 
126
217
  // Use provided accessibilityLabel, or fall back to label text
127
218
  const defaultAccessibilityLabel = accessibilityLabel || (typeof label === 'string' ? label : undefined);
@@ -134,14 +225,32 @@ function Button({
134
225
  accessibilityLabel: defaultAccessibilityLabel,
135
226
  webAccessibilityProps
136
227
  });
137
- const hoverStyle = {
138
- backgroundColor: hoverBg,
139
- borderColor: hoverBorderColor
140
- };
141
- const pressedStyle = {
142
- backgroundColor: pressedBg,
143
- borderColor: pressedBorderColor
144
- };
228
+
229
+ // Stable handler identities. The user's handlers are read through a ref so
230
+ // these wrappers do not need to be re-created when the user passes inline
231
+ // closures, which would defeat React.memo on Pressable.
232
+ const handlePressIn = useCallback(e => {
233
+ userHandlersRef.current.onPressIn?.(e);
234
+ }, []);
235
+ const handlePressOut = useCallback(e => {
236
+ userHandlersRef.current.onPressOut?.(e);
237
+ }, []);
238
+ const handleHoverIn = useCallback(e => {
239
+ if (IS_WEB) setIsHovered(true);
240
+ userHandlersRef.current.onHoverIn?.(e);
241
+ }, []);
242
+ const handleHoverOut = useCallback(e => {
243
+ if (IS_WEB) setIsHovered(false);
244
+ userHandlersRef.current.onHoverOut?.(e);
245
+ }, []);
246
+
247
+ // The Pressable style callback receives `pressed` from RN's gesture
248
+ // pipeline and applies the pressed visual to the host view directly,
249
+ // without going through React. This is the key to flicker-free behavior:
250
+ // a scroll-cancelled touch reverts the style on the native side only.
251
+ const styleCallback = useCallback(({
252
+ pressed
253
+ }) => [tokens.container, isHovered && !disabled ? tokens.hoverContainer : null, pressed && !disabled ? tokens.pressedContainer : null, style], [tokens.container, tokens.hoverContainer, tokens.pressedContainer, isHovered, disabled, style]);
145
254
  if (__DEV__) {
146
255
  if (children && labelStyle) {
147
256
  console.warn('[Button] `children` was provided; `labelStyle` will be ignored. ' + 'Use `renderContent` if you need custom content that still reuses the label styles.');
@@ -164,25 +273,12 @@ function Button({
164
273
  ...(onPress !== undefined ? {
165
274
  onPress
166
275
  } : {}),
167
- onPressIn: e => {
168
- setIsPressed(true);
169
- rest?.onPressIn?.(e);
170
- },
171
- onPressOut: e => {
172
- setIsPressed(false);
173
- rest?.onPressOut?.(e);
174
- },
175
- onHoverIn: e => {
176
- setIsHovered(true);
177
- rest?.onHoverIn?.(e);
178
- },
179
- onHoverOut: e => {
180
- setIsHovered(false);
181
- rest?.onHoverOut?.(e);
182
- },
183
- style: ({
184
- pressed
185
- }) => [containerBaseStyle, isHovered && !disabled ? hoverStyle : null, (pressed || isPressed) && !disabled ? pressedStyle : null, style],
276
+ onPressIn: handlePressIn,
277
+ onPressOut: handlePressOut,
278
+ onHoverIn: handleHoverIn,
279
+ onHoverOut: handleHoverOut,
280
+ unstable_pressDelay: PRESS_DELAY,
281
+ style: styleCallback,
186
282
  ...webProps,
187
283
  children: [leading ? /*#__PURE__*/_jsx(View, {
188
284
  style: leadingAccessoryStyle,
@@ -191,7 +287,7 @@ function Button({
191
287
  style: trailingAccessoryStyle,
192
288
  children: /*#__PURE__*/_jsx(Icon, {
193
289
  name: icon,
194
- size: iconSize,
290
+ size: tokens.iconSize,
195
291
  color: activeIconColor,
196
292
  accessibilityElementsHidden: true,
197
293
  importantForAccessibility: "no"
@@ -202,4 +298,5 @@ function Button({
202
298
  }) : null]
203
299
  });
204
300
  }
301
+ const Button = /*#__PURE__*/React.memo(ButtonImpl);
205
302
  export default Button;
@@ -3,7 +3,7 @@
3
3
  import React from 'react';
4
4
  import { View } from 'react-native';
5
5
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
6
- import { flattenChildren } from '../../utils/react-utils';
6
+ import { EMPTY_MODES, flattenChildren } from '../../utils/react-utils';
7
7
  import IconButton from '../IconButton/IconButton';
8
8
  import { jsx as _jsx } from "react/jsx-runtime";
9
9
  /**
@@ -25,7 +25,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
25
25
  */
26
26
  function ButtonGroup({
27
27
  children,
28
- modes = {},
28
+ modes = EMPTY_MODES,
29
29
  style
30
30
  }) {
31
31
  // Resolve design tokens
@@ -3,6 +3,7 @@
3
3
  import React, { createContext, useContext, isValidElement, cloneElement } from 'react';
4
4
  import { View, Text } from 'react-native';
5
5
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
6
+ import { EMPTY_MODES } from '../../utils/react-utils';
6
7
 
7
8
  /**
8
9
  * Context to share 'modes' with child components like Card.Title and Card.SupportText.
@@ -27,7 +28,7 @@ const CardContext = /*#__PURE__*/createContext({});
27
28
  export function Card({
28
29
  media,
29
30
  children,
30
- modes = {},
31
+ modes = EMPTY_MODES,
31
32
  mediaAspectRatio = 154 / 116,
32
33
  style
33
34
  }) {
@@ -4,7 +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 { useTokens } from '../../design-tokens/JFSThemeProvider';
7
- import { cloneChildrenWithModes } from '../../utils/react-utils';
7
+ import { cloneChildrenWithModes, EMPTY_MODES } from '../../utils/react-utils';
8
8
  import IconCapsule from '../IconCapsule/IconCapsule';
9
9
  import Button from '../Button/Button';
10
10
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
@@ -14,7 +14,7 @@ function CardCTA({
14
14
  iconName = 'ic_upi_number',
15
15
  buttonLabel = 'Button',
16
16
  onPressButton,
17
- modes: propModes = {},
17
+ modes: propModes = EMPTY_MODES,
18
18
  iconSlot,
19
19
  buttonSlot,
20
20
  style