jfs-components 0.0.62 → 0.0.64

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (255) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/lib/commonjs/components/Accordion/Accordion.js +1 -1
  3. package/lib/commonjs/components/ActionFooter/ActionFooter.js +1 -1
  4. package/lib/commonjs/components/ActionTile/ActionTile.js +2 -1
  5. package/lib/commonjs/components/AmountInput/AmountInput.js +2 -1
  6. package/lib/commonjs/components/AppBar/AppBar.js +1 -1
  7. package/lib/commonjs/components/Avatar/Avatar.js +184 -162
  8. package/lib/commonjs/components/AvatarGroup/AvatarGroup.js +1 -1
  9. package/lib/commonjs/components/Badge/Badge.js +2 -1
  10. package/lib/commonjs/components/Balance/Balance.js +2 -1
  11. package/lib/commonjs/components/BottomNav/BottomNav.js +2 -1
  12. package/lib/commonjs/components/BottomNavItem/BottomNavItem.js +106 -86
  13. package/lib/commonjs/components/Button/Button.js +190 -93
  14. package/lib/commonjs/components/ButtonGroup/ButtonGroup.js +1 -1
  15. package/lib/commonjs/components/Card/Card.js +2 -1
  16. package/lib/commonjs/components/CardCTA/CardCTA.js +1 -1
  17. package/lib/commonjs/components/CardProviderInfo/CardProviderInfo.js +1 -1
  18. package/lib/commonjs/components/Carousel/Carousel.js +3 -2
  19. package/lib/commonjs/components/Checkbox/Checkbox.js +2 -1
  20. package/lib/commonjs/components/ChipGroup/ChipGroup.js +1 -1
  21. package/lib/commonjs/components/ChipSelect/ChipSelect.js +2 -1
  22. package/lib/commonjs/components/DebitCard/DebitCard.js +1 -1
  23. package/lib/commonjs/components/Disclaimer/Disclaimer.js +2 -1
  24. package/lib/commonjs/components/Divider/Divider.js +2 -1
  25. package/lib/commonjs/components/Drawer/Drawer.js +109 -48
  26. package/lib/commonjs/components/EmptyState/EmptyState.js +2 -1
  27. package/lib/commonjs/components/FilterBar/FilterBar.js +1 -1
  28. package/lib/commonjs/components/Form/Form.js +2 -1
  29. package/lib/commonjs/components/FormField/FormField.js +3 -2
  30. package/lib/commonjs/components/HStack/HStack.js +1 -1
  31. package/lib/commonjs/components/HoldingsCard/HoldingsCard.js +2 -1
  32. package/lib/commonjs/components/IconButton/IconButton.js +118 -128
  33. package/lib/commonjs/components/IconCapsule/IconCapsule.js +61 -57
  34. package/lib/commonjs/components/InputSearch/InputSearch.js +7 -3
  35. package/lib/commonjs/components/LazyList/LazyList.js +1 -1
  36. package/lib/commonjs/components/LinearMeter/LinearMeter.js +3 -2
  37. package/lib/commonjs/components/ListGroup/ListGroup.js +1 -1
  38. package/lib/commonjs/components/ListItem/ListItem.js +190 -142
  39. package/lib/commonjs/components/MediaCard/MediaCard.js +3 -3
  40. package/lib/commonjs/components/MerchantProfile/MerchantProfile.js +2 -1
  41. package/lib/commonjs/components/MoneyValue/MoneyValue.js +2 -1
  42. package/lib/commonjs/components/NavArrow/NavArrow.js +82 -59
  43. package/lib/commonjs/components/NoteInput/NoteInput.js +2 -1
  44. package/lib/commonjs/components/Nudge/Nudge.js +1 -1
  45. package/lib/commonjs/components/Numpad/Numpad.js +2 -1
  46. package/lib/commonjs/components/OTP/OTP.js +1 -1
  47. package/lib/commonjs/components/PaymentFeedback/PaymentFeedback.js +2 -1
  48. package/lib/commonjs/components/Popup/Popup.js +2 -1
  49. package/lib/commonjs/components/ProductLabel/ProductLabel.js +2 -1
  50. package/lib/commonjs/components/ProgressBadge/ProgressBadge.js +2 -1
  51. package/lib/commonjs/components/RadioButton/RadioButton.js +2 -1
  52. package/lib/commonjs/components/RechargeCard/RechargeCard.js +2 -1
  53. package/lib/commonjs/components/Screen/Screen.js +1 -1
  54. package/lib/commonjs/components/Section/Section.js +500 -166
  55. package/lib/commonjs/components/SegmentedControl/SegmentedControl.js +3 -2
  56. package/lib/commonjs/components/StatItem/StatItem.js +2 -1
  57. package/lib/commonjs/components/StatusHero/StatusHero.js +2 -1
  58. package/lib/commonjs/components/Stepper/Step.js +2 -1
  59. package/lib/commonjs/components/Stepper/StepLabel.js +2 -1
  60. package/lib/commonjs/components/Stepper/Stepper.js +2 -1
  61. package/lib/commonjs/components/SupportText/SupportText.js +2 -1
  62. package/lib/commonjs/components/SupportText/SupportTextIcon.js +2 -1
  63. package/lib/commonjs/components/SwappableAmount/SwappableAmount.js +2 -1
  64. package/lib/commonjs/components/Tabs/TabItem.js +2 -1
  65. package/lib/commonjs/components/Tabs/Tabs.js +2 -1
  66. package/lib/commonjs/components/Text/Text.js +2 -1
  67. package/lib/commonjs/components/TextInput/TextInput.js +2 -2
  68. package/lib/commonjs/components/ThreadHero/ThreadHero.js +2 -1
  69. package/lib/commonjs/components/Title/Title.js +2 -1
  70. package/lib/commonjs/components/Toast/Toast.js +2 -1
  71. package/lib/commonjs/components/Toggle/Toggle.js +2 -1
  72. package/lib/commonjs/components/Tooltip/Tooltip.js +2 -1
  73. package/lib/commonjs/components/TransactionBubble/TransactionBubble.js +1 -1
  74. package/lib/commonjs/components/TransactionDetails/TransactionDetails.js +2 -2
  75. package/lib/commonjs/components/TransactionStatus/TransactionStatus.js +3 -2
  76. package/lib/commonjs/components/UpiHandle/UpiHandle.js +144 -110
  77. package/lib/commonjs/components/VStack/VStack.js +1 -1
  78. package/lib/commonjs/design-tokens/figma-variables-resolver.js +21 -3
  79. package/lib/commonjs/icons/registry.js +1 -1
  80. package/lib/commonjs/utils/react-utils.js +17 -0
  81. package/lib/module/components/Accordion/Accordion.js +2 -2
  82. package/lib/module/components/ActionFooter/ActionFooter.js +2 -2
  83. package/lib/module/components/ActionTile/ActionTile.js +2 -1
  84. package/lib/module/components/AmountInput/AmountInput.js +2 -1
  85. package/lib/module/components/AppBar/AppBar.js +2 -2
  86. package/lib/module/components/Avatar/Avatar.js +184 -162
  87. package/lib/module/components/AvatarGroup/AvatarGroup.js +2 -2
  88. package/lib/module/components/Badge/Badge.js +2 -1
  89. package/lib/module/components/Balance/Balance.js +2 -1
  90. package/lib/module/components/BottomNav/BottomNav.js +2 -1
  91. package/lib/module/components/BottomNavItem/BottomNavItem.js +108 -88
  92. package/lib/module/components/Button/Button.js +192 -95
  93. package/lib/module/components/ButtonGroup/ButtonGroup.js +2 -2
  94. package/lib/module/components/Card/Card.js +2 -1
  95. package/lib/module/components/CardCTA/CardCTA.js +2 -2
  96. package/lib/module/components/CardProviderInfo/CardProviderInfo.js +2 -2
  97. package/lib/module/components/Carousel/Carousel.js +3 -2
  98. package/lib/module/components/Checkbox/Checkbox.js +2 -1
  99. package/lib/module/components/ChipGroup/ChipGroup.js +2 -2
  100. package/lib/module/components/ChipSelect/ChipSelect.js +2 -1
  101. package/lib/module/components/DebitCard/DebitCard.js +2 -2
  102. package/lib/module/components/Disclaimer/Disclaimer.js +2 -1
  103. package/lib/module/components/Divider/Divider.js +2 -1
  104. package/lib/module/components/Drawer/Drawer.js +109 -48
  105. package/lib/module/components/EmptyState/EmptyState.js +2 -1
  106. package/lib/module/components/FilterBar/FilterBar.js +2 -2
  107. package/lib/module/components/Form/Form.js +2 -1
  108. package/lib/module/components/FormField/FormField.js +3 -2
  109. package/lib/module/components/HStack/HStack.js +2 -2
  110. package/lib/module/components/HoldingsCard/HoldingsCard.js +2 -1
  111. package/lib/module/components/IconButton/IconButton.js +120 -130
  112. package/lib/module/components/IconCapsule/IconCapsule.js +60 -57
  113. package/lib/module/components/InputSearch/InputSearch.js +7 -3
  114. package/lib/module/components/LazyList/LazyList.js +2 -2
  115. package/lib/module/components/LinearMeter/LinearMeter.js +3 -2
  116. package/lib/module/components/ListGroup/ListGroup.js +2 -2
  117. package/lib/module/components/ListItem/ListItem.js +194 -146
  118. package/lib/module/components/MediaCard/MediaCard.js +4 -2
  119. package/lib/module/components/MerchantProfile/MerchantProfile.js +2 -1
  120. package/lib/module/components/MoneyValue/MoneyValue.js +2 -1
  121. package/lib/module/components/NavArrow/NavArrow.js +82 -58
  122. package/lib/module/components/NoteInput/NoteInput.js +2 -1
  123. package/lib/module/components/Nudge/Nudge.js +2 -2
  124. package/lib/module/components/Numpad/Numpad.js +2 -1
  125. package/lib/module/components/OTP/OTP.js +2 -2
  126. package/lib/module/components/PaymentFeedback/PaymentFeedback.js +2 -1
  127. package/lib/module/components/Popup/Popup.js +2 -1
  128. package/lib/module/components/ProductLabel/ProductLabel.js +2 -1
  129. package/lib/module/components/ProgressBadge/ProgressBadge.js +2 -1
  130. package/lib/module/components/RadioButton/RadioButton.js +2 -1
  131. package/lib/module/components/RechargeCard/RechargeCard.js +2 -1
  132. package/lib/module/components/Screen/Screen.js +2 -2
  133. package/lib/module/components/Section/Section.js +503 -169
  134. package/lib/module/components/SegmentedControl/SegmentedControl.js +3 -2
  135. package/lib/module/components/StatItem/StatItem.js +2 -1
  136. package/lib/module/components/StatusHero/StatusHero.js +2 -1
  137. package/lib/module/components/Stepper/Step.js +2 -1
  138. package/lib/module/components/Stepper/StepLabel.js +2 -1
  139. package/lib/module/components/Stepper/Stepper.js +2 -1
  140. package/lib/module/components/SupportText/SupportText.js +2 -1
  141. package/lib/module/components/SupportText/SupportTextIcon.js +2 -1
  142. package/lib/module/components/SwappableAmount/SwappableAmount.js +2 -1
  143. package/lib/module/components/Tabs/TabItem.js +2 -1
  144. package/lib/module/components/Tabs/Tabs.js +2 -1
  145. package/lib/module/components/Text/Text.js +2 -1
  146. package/lib/module/components/TextInput/TextInput.js +3 -3
  147. package/lib/module/components/ThreadHero/ThreadHero.js +2 -1
  148. package/lib/module/components/Title/Title.js +2 -1
  149. package/lib/module/components/Toast/Toast.js +2 -1
  150. package/lib/module/components/Toggle/Toggle.js +2 -1
  151. package/lib/module/components/Tooltip/Tooltip.js +2 -1
  152. package/lib/module/components/TransactionBubble/TransactionBubble.js +2 -2
  153. package/lib/module/components/TransactionDetails/TransactionDetails.js +3 -3
  154. package/lib/module/components/TransactionStatus/TransactionStatus.js +3 -2
  155. package/lib/module/components/UpiHandle/UpiHandle.js +147 -113
  156. package/lib/module/components/VStack/VStack.js +2 -2
  157. package/lib/module/design-tokens/figma-variables-resolver.js +21 -3
  158. package/lib/module/icons/registry.js +1 -1
  159. package/lib/module/utils/react-utils.js +16 -0
  160. package/lib/typescript/src/components/Avatar/Avatar.d.ts +11 -17
  161. package/lib/typescript/src/components/BottomNavItem/BottomNavItem.d.ts +12 -8
  162. package/lib/typescript/src/components/Button/Button.d.ts +18 -1
  163. package/lib/typescript/src/components/IconButton/IconButton.d.ts +12 -29
  164. package/lib/typescript/src/components/IconCapsule/IconCapsule.d.ts +10 -18
  165. package/lib/typescript/src/components/InputSearch/InputSearch.d.ts +8 -3
  166. package/lib/typescript/src/components/ListItem/ListItem.d.ts +14 -1
  167. package/lib/typescript/src/components/NavArrow/NavArrow.d.ts +12 -11
  168. package/lib/typescript/src/components/Section/Section.d.ts +43 -48
  169. package/lib/typescript/src/components/UpiHandle/UpiHandle.d.ts +13 -12
  170. package/lib/typescript/src/icons/registry.d.ts +1 -1
  171. package/lib/typescript/src/utils/react-utils.d.ts +15 -0
  172. package/package.json +4 -6
  173. package/src/components/Accordion/Accordion.tsx +2 -2
  174. package/src/components/ActionFooter/ActionFooter.tsx +2 -2
  175. package/src/components/ActionTile/ActionTile.tsx +2 -1
  176. package/src/components/AmountInput/AmountInput.tsx +2 -1
  177. package/src/components/AppBar/AppBar.tsx +2 -2
  178. package/src/components/Avatar/Avatar.tsx +229 -158
  179. package/src/components/AvatarGroup/AvatarGroup.tsx +2 -2
  180. package/src/components/Badge/Badge.tsx +2 -1
  181. package/src/components/Balance/Balance.tsx +2 -1
  182. package/src/components/BottomNav/BottomNav.tsx +2 -1
  183. package/src/components/BottomNavItem/BottomNavItem.tsx +159 -88
  184. package/src/components/Button/Button.tsx +228 -101
  185. package/src/components/ButtonGroup/ButtonGroup.tsx +2 -2
  186. package/src/components/Card/Card.tsx +2 -1
  187. package/src/components/CardCTA/CardCTA.tsx +2 -2
  188. package/src/components/CardProviderInfo/CardProviderInfo.tsx +2 -2
  189. package/src/components/Carousel/Carousel.tsx +3 -2
  190. package/src/components/Checkbox/Checkbox.tsx +2 -1
  191. package/src/components/ChipGroup/ChipGroup.tsx +2 -2
  192. package/src/components/ChipSelect/ChipSelect.tsx +2 -1
  193. package/src/components/DebitCard/DebitCard.tsx +2 -2
  194. package/src/components/Disclaimer/Disclaimer.tsx +2 -1
  195. package/src/components/Divider/Divider.tsx +2 -1
  196. package/src/components/Drawer/Drawer.tsx +124 -58
  197. package/src/components/EmptyState/EmptyState.tsx +2 -1
  198. package/src/components/FilterBar/FilterBar.tsx +2 -2
  199. package/src/components/Form/Form.tsx +2 -1
  200. package/src/components/FormField/FormField.tsx +3 -2
  201. package/src/components/HStack/HStack.tsx +2 -2
  202. package/src/components/HoldingsCard/HoldingsCard.tsx +2 -1
  203. package/src/components/IconButton/IconButton.tsx +154 -126
  204. package/src/components/IconCapsule/IconCapsule.tsx +73 -54
  205. package/src/components/InputSearch/InputSearch.tsx +19 -5
  206. package/src/components/LazyList/LazyList.tsx +2 -2
  207. package/src/components/LinearMeter/LinearMeter.tsx +3 -2
  208. package/src/components/ListGroup/ListGroup.tsx +2 -2
  209. package/src/components/ListItem/ListItem.tsx +257 -187
  210. package/src/components/MediaCard/MediaCard.tsx +2 -1
  211. package/src/components/MerchantProfile/MerchantProfile.tsx +2 -1
  212. package/src/components/MoneyValue/MoneyValue.tsx +2 -1
  213. package/src/components/NavArrow/NavArrow.tsx +91 -58
  214. package/src/components/NoteInput/NoteInput.tsx +2 -1
  215. package/src/components/Nudge/Nudge.tsx +2 -2
  216. package/src/components/Numpad/Numpad.tsx +2 -1
  217. package/src/components/OTP/OTP.tsx +2 -2
  218. package/src/components/PaymentFeedback/PaymentFeedback.tsx +2 -1
  219. package/src/components/Popup/Popup.tsx +2 -1
  220. package/src/components/ProductLabel/ProductLabel.tsx +2 -1
  221. package/src/components/ProgressBadge/ProgressBadge.tsx +2 -2
  222. package/src/components/RadioButton/RadioButton.tsx +2 -1
  223. package/src/components/RechargeCard/RechargeCard.tsx +2 -1
  224. package/src/components/Screen/Screen.tsx +2 -2
  225. package/src/components/Section/Section.tsx +672 -176
  226. package/src/components/SegmentedControl/SegmentedControl.tsx +3 -2
  227. package/src/components/StatItem/StatItem.tsx +2 -1
  228. package/src/components/StatusHero/StatusHero.tsx +2 -1
  229. package/src/components/Stepper/Step.tsx +2 -1
  230. package/src/components/Stepper/StepLabel.tsx +2 -1
  231. package/src/components/Stepper/Stepper.tsx +2 -1
  232. package/src/components/SupportText/SupportText.tsx +2 -1
  233. package/src/components/SupportText/SupportTextIcon.tsx +2 -1
  234. package/src/components/SwappableAmount/SwappableAmount.tsx +2 -1
  235. package/src/components/Tabs/TabItem.tsx +2 -1
  236. package/src/components/Tabs/Tabs.tsx +2 -1
  237. package/src/components/Text/Text.tsx +2 -1
  238. package/src/components/TextInput/TextInput.tsx +3 -3
  239. package/src/components/ThreadHero/ThreadHero.tsx +2 -1
  240. package/src/components/Title/Title.tsx +2 -1
  241. package/src/components/Toast/Toast.tsx +2 -1
  242. package/src/components/Toggle/Toggle.tsx +2 -1
  243. package/src/components/Tooltip/Tooltip.tsx +2 -1
  244. package/src/components/TransactionBubble/TransactionBubble.tsx +2 -2
  245. package/src/components/TransactionDetails/TransactionDetails.tsx +3 -3
  246. package/src/components/TransactionStatus/TransactionStatus.tsx +3 -2
  247. package/src/components/UpiHandle/UpiHandle.tsx +193 -125
  248. package/src/components/VStack/VStack.tsx +2 -2
  249. package/src/design-tokens/figma-variables-resolver.ts +21 -3
  250. package/src/icons/registry.ts +1 -1
  251. package/src/utils/react-utils.ts +16 -0
  252. package/lib/typescript/App.d.ts +0 -2
  253. package/lib/typescript/index.d.ts +0 -2
  254. package/lib/typescript/metro.config.d.ts +0 -78
  255. package/lib/typescript/react-native.config.d.ts +0 -4
@@ -1,13 +1,16 @@
1
- import React, { useState } from 'react'
1
+ import React, { useCallback, useMemo, useRef, useState } from 'react'
2
2
  import {
3
+ Platform,
3
4
  Pressable,
4
5
  type AccessibilityState,
6
+ type PressableStateCallbackType,
5
7
  type StyleProp,
6
8
  type ViewStyle,
7
9
  } from 'react-native'
8
10
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
9
11
  import Icon from '../../icons/Icon'
10
12
  import { usePressableWebSupport, type SafePressableProps, type WebAccessibilityProps } from '../../utils/web-platform-utils'
13
+ import { EMPTY_MODES } from '../../utils/react-utils'
11
14
 
12
15
  type IconButtonProps = SafePressableProps & {
13
16
  iconName?: string;
@@ -29,7 +32,7 @@ type IconButtonProps = SafePressableProps & {
29
32
  /**
30
33
  * Icon to display when isToggle is true and isActive is false
31
34
  */
32
- inactiveIcon?: string; // Fixed typo in previous thought, user said "inactive icon"
35
+ inactiveIcon?: string;
33
36
  /**
34
37
  * Whether the toggle button is in active state (only used when isToggle is true)
35
38
  */
@@ -40,41 +43,75 @@ type IconButtonProps = SafePressableProps & {
40
43
  webAccessibilityProps?: WebAccessibilityProps;
41
44
  };
42
45
 
46
+ // ---------------------------------------------------------------------------
47
+ // Module-scope constants
48
+ // ---------------------------------------------------------------------------
49
+
50
+ const IS_WEB = Platform.OS === 'web'
51
+ const IS_IOS = Platform.OS === 'ios'
52
+ const PRESS_DELAY = IS_IOS ? 130 : 0
53
+
54
+ // Pressed visual is applied through Pressable's style callback so the host
55
+ // view updates without scheduling a React render. Hover/focus stay mirrored
56
+ // in React state because they're sustained visual states (web only).
57
+ const pressedOverlayStyle: ViewStyle = { opacity: 0.7 }
58
+ const hoverOverlayStyle: ViewStyle = { opacity: 0.85 }
59
+ const focusOverlayStyle: ViewStyle = { borderWidth: 1, borderColor: '#222' }
60
+
61
+ interface IconButtonTokens {
62
+ baseContainerStyle: ViewStyle;
63
+ iconColor: string;
64
+ iconSize: number;
65
+ }
66
+
67
+ function resolveIconButtonTokens(modes: Record<string, any>, disabled: boolean): IconButtonTokens {
68
+ const radiusRaw = (getVariableByName('iconButton/radius', modes) ?? 9999) as number
69
+ const padding = (getVariableByName('iconButton/padding', modes) ?? 12) as number
70
+ const backgroundColor = (getVariableByName('iconButton/background', modes) ?? '#cfa159') as string
71
+ const borderColor = (getVariableByName('iconButton/border/color', modes) ?? '#ffffff00') as string
72
+ const borderSize = (getVariableByName('iconButton/border/size', modes) ?? 1) as number
73
+ const iconColor = (getVariableByName('iconButton/icon/color', modes) ?? '#0f0d0a') as string
74
+ const iconSize = (getVariableByName('iconButton/icon/size', modes) ?? 18) as number
75
+
76
+ // 9999 is the design-token sentinel for "perfect circle"
77
+ const buttonSize = padding * 2 + iconSize
78
+ const borderRadius = radiusRaw === 9999 ? buttonSize / 2 : radiusRaw
79
+
80
+ return {
81
+ baseContainerStyle: {
82
+ width: buttonSize,
83
+ height: buttonSize,
84
+ borderRadius,
85
+ borderWidth: borderSize,
86
+ borderColor,
87
+ backgroundColor,
88
+ overflow: 'hidden',
89
+ alignItems: 'center',
90
+ justifyContent: 'center',
91
+ padding,
92
+ opacity: disabled ? 0.5 : 1,
93
+ },
94
+ iconColor,
95
+ iconSize,
96
+ }
97
+ }
98
+
43
99
  /**
44
100
  * IconButton component that displays an icon within a pressable button container.
45
- *
46
- * This component displays an icon within a styled button container that can be pressed.
47
- * All styling values are resolved from Figma design tokens using the provided modes.
48
- *
49
- * @component
50
- * @param {Object} props - Component props
51
- * @param {string} [props.iconName="ic_card"] - The name of the icon to display from the icon registry
52
- * @param {Object} [props.modes={}] - Mode configuration for design tokens (e.g., {"Button / Size": "M", "Appearance": "high"})
53
- * @param {Function} [props.onPress] - Callback function called when the button is pressed
54
- * @param {boolean} [props.disabled=false] - Whether the button is disabled
55
- * @param {Object} [props.style] - Additional styles for the container
56
- * @param {string} [props.accessibilityLabel] - Accessibility label for screen readers. If not provided, defaults to iconName
57
- * @param {string} [props.accessibilityHint] - Additional accessibility hint for screen readers
58
- * @param {Object} [props.accessibilityState] - Additional accessibility state information
59
- *
60
- * @example
61
- * ```jsx
62
- * // Default icon button
63
- * <IconButton modes={{}} />
64
- *
65
- * // Custom icon with size mode
66
- * <IconButton iconName="ic_rupee" modes={{"Button / Size": "M"}} />
67
- *
68
- * // With appearance mode
69
- * <IconButton modes={{"Appearance": "high"}} onPress={() => console.log('pressed')} />
70
- *
71
- * // With accessibility label
72
- * <IconButton iconName="ic_search" accessibilityLabel={undefined} accessibilityHint="Opens the search screen" />
73
- * ```
101
+ *
102
+ * Performance notes:
103
+ * - Token reads collapsed into a single `useMemo([modes, disabled, isToggle, isActive])`.
104
+ * - Press visual goes through Pressable's `({ pressed })` style callback so
105
+ * a scroll-cancelled touch never schedules a React render. iOS gets
106
+ * `unstable_pressDelay={130}`.
107
+ * - Hover and focus state are mirrored on web only (gated setters).
108
+ * - The previous version had a redundant `manualPressStyle` (a duplicate
109
+ * pressed transform mirrored via React state) removed.
110
+ * - Wrapped in `React.memo`.
74
111
  */
75
112
  function IconButton({
76
- iconName = "ic_card",
77
- modes = {},
113
+ iconName = 'ic_card',
114
+ modes = EMPTY_MODES,
78
115
  onPress,
79
116
  disabled = false,
80
117
  style,
@@ -88,64 +125,52 @@ function IconButton({
88
125
  isActive = false,
89
126
  ...rest
90
127
  }: IconButtonProps) {
128
+ // Merge explicit props with modes for token resolution. Memoize the merged
129
+ // object so its identity is stable when none of the inputs change — that
130
+ // keeps the resolver's WeakMap cache hot.
131
+ const componentModes = useMemo(
132
+ () => ({
133
+ ...modes,
134
+ isToggle,
135
+ isActive,
136
+ }),
137
+ [modes, isToggle, isActive]
138
+ )
139
+
140
+ const tokens = useMemo(
141
+ () => resolveIconButtonTokens(componentModes, disabled),
142
+ [componentModes, disabled]
143
+ )
144
+
91
145
  const [isFocused, setIsFocused] = useState(false)
92
146
  const [isHovered, setIsHovered] = useState(false)
93
- const [isPressed, setIsPressed] = useState(false)
94
-
95
-
96
- // Merge explicit props with modes for token resolution
97
- // This allows the variable resolver to pick up isToggle and isActive specific tokens
98
- const componentModes = {
99
- ...modes,
100
- 'isToggle': isToggle,
101
- 'isActive': isActive,
102
- };
103
-
104
- // Resolve design tokens using only the Figma-defined base tokens for Icon Button (node 72:14)
105
- const radius = getVariableByName('iconButton/radius', componentModes) ?? 9999
106
- const padding = getVariableByName('iconButton/padding', componentModes) ?? 12
107
- const backgroundColor = getVariableByName('iconButton/background', componentModes) ?? '#cfa159'
108
- const borderColor = getVariableByName('iconButton/border/color', componentModes) ?? '#ffffff00'
109
- const borderSize = getVariableByName('iconButton/border/size', componentModes) ?? 1
110
- const iconColor = getVariableByName('iconButton/icon/color', componentModes) ?? '#0f0d0a'
111
- const iconSize = getVariableByName('iconButton/icon/size', componentModes) ?? 18
112
147
 
113
- // Determine which icon to display
114
- // If isToggle is yes, use active/inactive icons based on isActive state
115
- // Otherwise use standard iconName
116
- let finalIconName = iconName;
117
- if (isToggle) {
118
- if (isActive && activeIcon) {
119
- finalIconName = activeIcon;
120
- } else if (!isActive && inactiveIcon) {
121
- finalIconName = inactiveIcon;
122
- }
123
- }
148
+ const userHandlersRef = useRef<{
149
+ onPressIn?: (e: any) => void
150
+ onPressOut?: (e: any) => void
151
+ onFocus?: (e: any) => void
152
+ onBlur?: (e: any) => void
153
+ onHoverIn?: (e: any) => void
154
+ onHoverOut?: (e: any) => void
155
+ }>({})
156
+ userHandlersRef.current.onPressIn = (rest as any)?.onPressIn
157
+ userHandlersRef.current.onPressOut = (rest as any)?.onPressOut
158
+ userHandlersRef.current.onFocus = (rest as any)?.onFocus
159
+ userHandlersRef.current.onBlur = (rest as any)?.onBlur
160
+ userHandlersRef.current.onHoverIn = (rest as any)?.onHoverIn
161
+ userHandlersRef.current.onHoverOut = (rest as any)?.onHoverOut
124
162
 
125
- // Convert radius to React Native format (if 9999, use padding*2 for perfect circle)
126
- // For a button, the size is determined by padding, so radius should be padding*2 for a circle
127
- const buttonSize = padding * 2 + iconSize
128
- const borderRadius = radius === 9999 ? buttonSize / 2 : radius
129
-
130
- // Container base style
131
- const baseContainerStyle: ViewStyle = {
132
- width: buttonSize,
133
- height: buttonSize,
134
- borderRadius: borderRadius,
135
- borderWidth: borderSize,
136
- borderColor: borderColor,
137
- backgroundColor: backgroundColor,
138
- overflow: 'hidden',
139
- alignItems: 'center',
140
- justifyContent: 'center',
141
- padding: padding,
142
- opacity: disabled ? 0.5 : 1,
143
- }
163
+ // Determine which icon to display
164
+ const finalIconName =
165
+ isToggle
166
+ ? (isActive && activeIcon
167
+ ? activeIcon
168
+ : (!isActive && inactiveIcon ? inactiveIcon : iconName))
169
+ : iconName
144
170
 
145
171
  // Generate default accessibility label from icon name if not provided
146
172
  const defaultAccessibilityLabel = accessibilityLabel || iconName.replace(/^ic_/, '').replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())
147
173
 
148
- // Get web platform support props
149
174
  const webProps = usePressableWebSupport({
150
175
  restProps: rest,
151
176
  onPress: disabled ? undefined : onPress,
@@ -154,6 +179,40 @@ function IconButton({
154
179
  webAccessibilityProps,
155
180
  })
156
181
 
182
+ const handlePressIn = useCallback((e: any) => {
183
+ userHandlersRef.current.onPressIn?.(e)
184
+ }, [])
185
+ const handlePressOut = useCallback((e: any) => {
186
+ userHandlersRef.current.onPressOut?.(e)
187
+ }, [])
188
+ const handleFocus = useCallback((e: any) => {
189
+ if (IS_WEB) setIsFocused(true)
190
+ userHandlersRef.current.onFocus?.(e)
191
+ }, [])
192
+ const handleBlur = useCallback((e: any) => {
193
+ if (IS_WEB) setIsFocused(false)
194
+ userHandlersRef.current.onBlur?.(e)
195
+ }, [])
196
+ const handleHoverIn = useCallback((e: any) => {
197
+ if (IS_WEB) setIsHovered(true)
198
+ userHandlersRef.current.onHoverIn?.(e)
199
+ }, [])
200
+ const handleHoverOut = useCallback((e: any) => {
201
+ if (IS_WEB) setIsHovered(false)
202
+ userHandlersRef.current.onHoverOut?.(e)
203
+ }, [])
204
+
205
+ const styleCallback = useCallback(
206
+ ({ pressed }: PressableStateCallbackType): StyleProp<ViewStyle> => [
207
+ tokens.baseContainerStyle,
208
+ style,
209
+ pressed && !disabled ? pressedOverlayStyle : null,
210
+ isHovered && !disabled ? hoverOverlayStyle : null,
211
+ isFocused && !disabled ? focusOverlayStyle : null,
212
+ ],
213
+ [tokens.baseContainerStyle, style, isHovered, isFocused, disabled]
214
+ )
215
+
157
216
  return (
158
217
  <Pressable
159
218
  accessibilityRole="button"
@@ -161,54 +220,24 @@ function IconButton({
161
220
  accessibilityHint={accessibilityHint}
162
221
  accessibilityState={{
163
222
  disabled,
164
- ...accessibilityState
223
+ ...accessibilityState,
165
224
  }}
166
225
  onPress={onPress}
167
226
  disabled={disabled}
168
- onPressIn={(e: any) => {
169
- setIsPressed(true)
170
- ; (rest as any)?.onPressIn?.(e)
171
- }}
172
- onPressOut={(e: any) => {
173
- setIsPressed(false)
174
- ; (rest as any)?.onPressOut?.(e)
175
- }}
176
- onFocus={(e: any) => {
177
- setIsFocused(true)
178
- ; (rest as any)?.onFocus?.(e)
179
- }}
180
- onBlur={(e: any) => {
181
- setIsFocused(false)
182
- ; (rest as any)?.onBlur?.(e)
183
- }}
184
- onHoverIn={(e: any) => {
185
- setIsHovered(true)
186
- ; (rest as any)?.onHoverIn?.(e)
187
- }}
188
- onHoverOut={(e: any) => {
189
- setIsHovered(false)
190
- ; (rest as any)?.onHoverOut?.(e)
191
- }}
192
- style={({ pressed }) => {
193
- const pressedStyle = pressed && !disabled ? { opacity: 0.7 } : null
194
- const hoverStyle = isHovered && !disabled ? { opacity: 0.85 } : null
195
- const focusStyle = isFocused && !disabled ? { borderWidth: 1, borderColor: '#222' } : null
196
- const manualPressStyle = isPressed && !disabled ? { transform: [{ scale: 0.98 }] } : null
197
- return [
198
- baseContainerStyle,
199
- style,
200
- pressedStyle,
201
- hoverStyle,
202
- focusStyle,
203
- manualPressStyle,
204
- ] as StyleProp<ViewStyle>
205
- }}
227
+ onPressIn={handlePressIn}
228
+ onPressOut={handlePressOut}
229
+ onFocus={handleFocus}
230
+ onBlur={handleBlur}
231
+ onHoverIn={handleHoverIn}
232
+ onHoverOut={handleHoverOut}
233
+ unstable_pressDelay={PRESS_DELAY}
234
+ style={styleCallback}
206
235
  {...webProps}
207
236
  >
208
237
  <Icon
209
238
  name={finalIconName}
210
- size={iconSize}
211
- color={iconColor}
239
+ size={tokens.iconSize}
240
+ color={tokens.iconColor}
212
241
  accessibilityElementsHidden={true}
213
242
  importantForAccessibility="no"
214
243
  />
@@ -216,5 +245,4 @@ function IconButton({
216
245
  )
217
246
  }
218
247
 
219
- export default IconButton
220
-
248
+ export default React.memo(IconButton)
@@ -1,7 +1,8 @@
1
- import React from 'react'
1
+ import React, { useMemo } from 'react'
2
2
  import { View, type StyleProp, type ViewStyle } from 'react-native'
3
3
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
4
4
  import { useTokens } from '../../design-tokens/JFSThemeProvider'
5
+ import { EMPTY_MODES } from '../../utils/react-utils'
5
6
  import Icon from '../../icons/Icon'
6
7
 
7
8
  type IconCapsuleProps = {
@@ -11,82 +12,101 @@ type IconCapsuleProps = {
11
12
  accessibilityRole?: string;
12
13
  } & React.ComponentProps<typeof View>;
13
14
 
15
+ interface IconCapsuleTokens {
16
+ containerStyle: ViewStyle;
17
+ iconColor: string;
18
+ iconSize: number;
19
+ }
20
+
21
+ function resolveIconCapsuleTokens(modes: Record<string, any>): IconCapsuleTokens {
22
+ const size = (getVariableByName('iconCapsule/size', modes) || 42) as number
23
+ const radiusRaw = (getVariableByName('iconCapsule/radius', modes) || 9999) as number
24
+ const backgroundColor = (getVariableByName('iconCapsule/background', modes) || '#cfa159') as string
25
+ const borderColor = (getVariableByName('iconCapsule/border/color', modes) || 'rgba(255,255,255,0)') as string
26
+ const borderSize = (getVariableByName('iconCapsule/border/size', modes) || 1) as number
27
+ const iconColor = (getVariableByName('iconCapsule/icon/color', modes) || '#0f0d0a') as string
28
+ const iconSize = (getVariableByName('iconCapsule/icon/size', modes) || 18) as number
29
+
30
+ // 9999 is the design-token sentinel for "perfect circle"
31
+ const borderRadius = radiusRaw === 9999 ? size / 2 : radiusRaw
32
+
33
+ return {
34
+ containerStyle: {
35
+ width: size,
36
+ height: size,
37
+ borderRadius,
38
+ borderWidth: borderSize,
39
+ borderColor,
40
+ backgroundColor,
41
+ overflow: 'hidden',
42
+ alignItems: 'center',
43
+ justifyContent: 'center',
44
+ },
45
+ iconColor,
46
+ iconSize,
47
+ }
48
+ }
49
+
14
50
  /**
15
51
  * IconCapsule component that displays an icon within a circular or rounded container.
16
- *
17
- * This component displays an icon (default: ic_card) within a styled capsule container.
52
+ *
18
53
  * All styling values are resolved from Figma design tokens using the provided modes.
19
- *
54
+ *
20
55
  * @component
21
56
  * @param {Object} props - Component props
22
57
  * @param {string} [props.iconName="ic_card"] - The name of the icon to display from the icon registry
23
58
  * @param {Object} [props.modes={}] - Mode configuration for design tokens (e.g., {"Appearance": "Primary"})
24
- * @param {string} [props.accessibilityLabel] - Accessibility label for screen readers. If not provided, defaults to iconName
59
+ * @param {string} [props.accessibilityLabel] - Accessibility label for screen readers
25
60
  * @param {string} [props.accessibilityRole] - Accessibility role (defaults to "image" for decorative icons)
26
- *
27
- * @example
28
- * ```jsx
29
- * // Default icon (ic_card)
30
- * <IconCapsule modes={{}} />
31
- *
32
- * // Custom icon
33
- * <IconCapsule iconName="ic_rupee" modes={{}} />
34
- *
35
- * // With appearance mode
36
- * <IconCapsule modes={{"Appearance": "Secondary"}} />
37
- *
38
- * // With accessibility label
39
- * <IconCapsule iconName="ic_card" accessibilityLabel={undefined} />
40
- * ```
61
+ *
62
+ * Performance notes:
63
+ * - All token reads collapsed into a single `useMemo([modes])`. The merged
64
+ * `(globalModes + propModes)` object is also memoized so downstream
65
+ * `getVariableByName` calls hit the resolver's per-modes-object cache.
66
+ * - Wrapped in `React.memo`; with the shared `EMPTY_MODES` default the
67
+ * common path benefits from full memoization.
41
68
  */
42
69
  function IconCapsule({
43
- iconName = "ic_card",
44
- modes: propModes = {},
45
- accessibilityLabel,
46
- accessibilityRole = "image",
70
+ iconName = 'ic_card',
71
+ modes: propModes = EMPTY_MODES,
72
+ // accessibilityLabel is accepted on the type for API back-compat but the
73
+ // component intentionally renders `accessibilityLabel={undefined}` (icons
74
+ // are decorative; the surrounding component owns the label).
75
+ accessibilityLabel: _accessibilityLabel,
76
+ accessibilityRole = 'image',
77
+ style: styleProp,
47
78
  ...rest
48
79
  }: IconCapsuleProps) {
49
80
  const { modes: globalModes } = useTokens()
50
- const modes = { ...globalModes, ...propModes }
51
- // Resolve design tokens using the provided modes
52
- const size = getVariableByName('iconCapsule/size', modes) || 42
53
- const radius = getVariableByName('iconCapsule/radius', modes) || 9999
54
- const backgroundColor = getVariableByName('iconCapsule/background', modes) || '#cfa159'
55
- const borderColor = getVariableByName('iconCapsule/border/color', modes) || 'rgba(255,255,255,0)'
56
- const borderSize = getVariableByName('iconCapsule/border/size', modes) || 1
57
- const iconColor = getVariableByName('iconCapsule/icon/color', modes) || '#0f0d0a'
58
- const iconSize = getVariableByName('iconCapsule/icon/size', modes) || 18
59
81
 
60
- // Convert radius to React Native format (if 9999, use size/2 for perfect circle)
61
- const borderRadius = radius === 9999 ? size / 2 : radius
82
+ // Memoize the merged modes object so its identity is stable across renders
83
+ // when neither the global nor the prop modes change. This is what lets the
84
+ // resolver's WeakMap cache hit on every subsequent `getVariableByName`.
85
+ const modes = useMemo(
86
+ () => (globalModes === EMPTY_MODES && propModes === EMPTY_MODES
87
+ ? EMPTY_MODES
88
+ : { ...globalModes, ...propModes }),
89
+ [globalModes, propModes]
90
+ )
62
91
 
63
- // Container style
64
- const containerStyle: ViewStyle = {
65
- width: size,
66
- height: size,
67
- borderRadius: borderRadius,
68
- borderWidth: borderSize,
69
- borderColor: borderColor,
70
- backgroundColor: backgroundColor,
71
- overflow: 'hidden',
72
- alignItems: 'center',
73
- justifyContent: 'center',
74
- }
92
+ const tokens = useMemo(() => resolveIconCapsuleTokens(modes), [modes])
75
93
 
76
- // Generate default accessibility label from icon name if not provided
77
- const defaultAccessibilityLabel = accessibilityLabel || iconName.replace(/^ic_/, '').replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())
94
+ const composedStyle = useMemo<StyleProp<ViewStyle>>(
95
+ () => (styleProp ? [tokens.containerStyle, styleProp] : tokens.containerStyle),
96
+ [tokens.containerStyle, styleProp]
97
+ )
78
98
 
79
99
  return (
80
100
  <View
81
- style={containerStyle}
101
+ style={composedStyle}
82
102
  accessibilityRole={accessibilityRole}
83
103
  accessibilityLabel={undefined}
84
104
  {...rest}
85
105
  >
86
106
  <Icon
87
107
  name={iconName}
88
- size={iconSize}
89
- color={iconColor}
108
+ size={tokens.iconSize}
109
+ color={tokens.iconColor}
90
110
  accessibilityElementsHidden={true}
91
111
  importantForAccessibility="no"
92
112
  />
@@ -94,5 +114,4 @@ function IconCapsule({
94
114
  )
95
115
  }
96
116
 
97
- export default IconCapsule
98
-
117
+ export default React.memo(IconCapsule)
@@ -1,6 +1,7 @@
1
1
  import React, { useState } from 'react'
2
- import { View, Text, Pressable, type StyleProp, type ViewStyle, type TextStyle } from 'react-native'
2
+ import { View, Text, Pressable, type StyleProp, type ViewStyle, type TextStyle, type TextInputProps as RNTextInputProps } from 'react-native'
3
3
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
4
+ import { EMPTY_MODES } from '../../utils/react-utils'
4
5
  import Icon from '../../icons/Icon'
5
6
  import TextInput from '../TextInput/TextInput'
6
7
 
@@ -9,13 +10,19 @@ import TextInput from '../TextInput/TextInput'
9
10
  */
10
11
  type SupportTextProps = {
11
12
  label?: string;
13
+ /**
14
+ * Icon name from the icon registry (e.g. 'ic_info', 'ic_cart').
15
+ * Defaults to 'ic_info'.
16
+ */
17
+ iconName?: string;
12
18
  modes?: Record<string, any>;
13
19
  style?: StyleProp<ViewStyle>;
14
20
  }
15
21
 
16
22
  function SupportText({
17
23
  label = "Support Text",
18
- modes = {},
24
+ iconName = "ic_info",
25
+ modes = EMPTY_MODES,
19
26
  style
20
27
  }: SupportTextProps) {
21
28
  // Resolve variables
@@ -35,7 +42,7 @@ function SupportText({
35
42
  alignItems: 'center',
36
43
  gap: gap,
37
44
  }, style]}>
38
- <Icon name="ic_info" size={iconSize} color={foreground} />
45
+ <Icon name={iconName} size={iconSize} color={foreground} />
39
46
  <Text style={{
40
47
  color: foreground,
41
48
  fontSize: fontSize,
@@ -52,6 +59,11 @@ function SupportText({
52
59
  export type InputSearchProps = {
53
60
  supportText?: boolean;
54
61
  supportTextLabel?: string;
62
+ /**
63
+ * Icon name from the icon registry to render inside the support text
64
+ * (e.g. 'ic_info', 'ic_cart'). Defaults to 'ic_info'.
65
+ */
66
+ supportTextIcon?: string;
55
67
  modes?: Record<string, any>;
56
68
  containerStyle?: StyleProp<ViewStyle>;
57
69
 
@@ -66,12 +78,13 @@ export type InputSearchProps = {
66
78
  inputStyle?: StyleProp<TextStyle>;
67
79
  accessibilityLabel?: string;
68
80
  accessibilityHint?: string;
69
- }
81
+ } & Omit<RNTextInputProps, 'style' | 'onChangeText' | 'onFocus' | 'onBlur' | 'placeholder' | 'value'>;
70
82
 
71
83
  export default function InputSearch({
72
84
  supportText = true,
73
85
  supportTextLabel = "Support Text",
74
- modes = {},
86
+ supportTextIcon = "ic_info",
87
+ modes = EMPTY_MODES,
75
88
  containerStyle,
76
89
  placeholder = "Search",
77
90
  value,
@@ -158,6 +171,7 @@ export default function InputSearch({
158
171
  {supportText && (
159
172
  <SupportText
160
173
  label={supportTextLabel}
174
+ iconName={supportTextIcon}
161
175
  modes={componentModes}
162
176
  />
163
177
  )}
@@ -1,7 +1,7 @@
1
1
  import React from 'react'
2
2
  import { View, type StyleProp, type ViewStyle } from 'react-native'
3
3
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
4
- import { cloneChildrenWithModes } from '../../utils/react-utils'
4
+ import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils'
5
5
 
6
6
  type LazyListProps = {
7
7
  listGroupsSlot?: React.ReactNode;
@@ -32,7 +32,7 @@ type LazyListProps = {
32
32
  */
33
33
  function LazyList({
34
34
  listGroupsSlot,
35
- modes = {},
35
+ modes = EMPTY_MODES,
36
36
  style,
37
37
  accessibilityLabel = undefined,
38
38
  accessibilityHint,
@@ -1,6 +1,7 @@
1
1
  import React from 'react'
2
2
  import { View, Text, type StyleProp, type ViewStyle, type TextStyle, type DimensionValue } from 'react-native'
3
3
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
4
+ import { EMPTY_MODES } from '../../utils/react-utils'
4
5
  import MoneyValue from '../MoneyValue/MoneyValue'
5
6
 
6
7
  type LinearMeterLabelProps = {
@@ -11,7 +12,7 @@ type LinearMeterLabelProps = {
11
12
 
12
13
  const LinearMeterLabel = ({
13
14
  children,
14
- modes = {},
15
+ modes = EMPTY_MODES,
15
16
  style,
16
17
  ...rest
17
18
  }: LinearMeterLabelProps) => {
@@ -48,7 +49,7 @@ export type LinearMeterProps = {
48
49
 
49
50
  const LinearMeter = ({
50
51
  value = 0,
51
- modes = {},
52
+ modes = EMPTY_MODES,
52
53
  style,
53
54
  trackStyle,
54
55
  indicatorStyle,
@@ -6,7 +6,7 @@ import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
6
6
  * Helper function to recursively clone children and pass modes prop to components that accept it.
7
7
  * This ensures that all child components in slots receive the modes prop from the parent.
8
8
  */
9
- import { cloneChildrenWithModes, flattenChildren } from '../../utils/react-utils'
9
+ import { EMPTY_MODES, cloneChildrenWithModes, flattenChildren } from '../../utils/react-utils'
10
10
 
11
11
  type ListGroupProps = {
12
12
  label?: string;
@@ -47,7 +47,7 @@ function ListGroup({
47
47
  label = '',
48
48
  listGroupSlot,
49
49
  children,
50
- modes = {},
50
+ modes = EMPTY_MODES,
51
51
  style,
52
52
  accessibilityLabel,
53
53
  accessibilityHint,