@lifi/widget 3.25.0-beta.5 → 3.26.0

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 (274) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/dist/esm/components/AmountInput/AmountInput.js +75 -17
  3. package/dist/esm/components/AmountInput/AmountInput.js.map +1 -1
  4. package/dist/esm/components/AmountInput/AmountInput.style.d.ts +4 -0
  5. package/dist/esm/components/AmountInput/AmountInput.style.js +11 -0
  6. package/dist/esm/components/AmountInput/AmountInput.style.js.map +1 -1
  7. package/dist/esm/components/AmountInput/AmountInputAdornment.style.d.ts +1 -0
  8. package/dist/esm/components/AmountInput/AmountInputAdornment.style.js +64 -5
  9. package/dist/esm/components/AmountInput/AmountInputAdornment.style.js.map +1 -1
  10. package/dist/esm/components/AmountInput/AmountInputEndAdornment.js +20 -7
  11. package/dist/esm/components/AmountInput/AmountInputEndAdornment.js.map +1 -1
  12. package/dist/esm/components/AmountInput/PriceFormHelperText.js +41 -15
  13. package/dist/esm/components/AmountInput/PriceFormHelperText.js.map +1 -1
  14. package/dist/esm/components/AmountInput/PriceFormHelperText.style.d.ts +1 -0
  15. package/dist/esm/components/AmountInput/PriceFormHelperText.style.js +29 -0
  16. package/dist/esm/components/AmountInput/PriceFormHelperText.style.js.map +1 -0
  17. package/dist/esm/components/AppContainer.js +1 -0
  18. package/dist/esm/components/AppContainer.js.map +1 -1
  19. package/dist/esm/components/ChainSelect/useChainSelect.js +2 -4
  20. package/dist/esm/components/ChainSelect/useChainSelect.js.map +1 -1
  21. package/dist/esm/components/Chains/ChainList.d.ts +2 -3
  22. package/dist/esm/components/Chains/ChainList.js +4 -3
  23. package/dist/esm/components/Chains/ChainList.js.map +1 -1
  24. package/dist/esm/components/Chains/ChainList.style.js +10 -3
  25. package/dist/esm/components/Chains/ChainList.style.js.map +1 -1
  26. package/dist/esm/components/Chains/ChainListItem.d.ts +4 -1
  27. package/dist/esm/components/Chains/ChainListItem.js +14 -2
  28. package/dist/esm/components/Chains/ChainListItem.js.map +1 -1
  29. package/dist/esm/components/Chains/PinChainButton.d.ts +7 -0
  30. package/dist/esm/components/Chains/PinChainButton.js +34 -0
  31. package/dist/esm/components/Chains/PinChainButton.js.map +1 -0
  32. package/dist/esm/components/Chains/SelectChainContent.js +15 -16
  33. package/dist/esm/components/Chains/SelectChainContent.js.map +1 -1
  34. package/dist/esm/components/Chains/VirtualizedChainList.d.ts +2 -1
  35. package/dist/esm/components/Chains/VirtualizedChainList.js +45 -9
  36. package/dist/esm/components/Chains/VirtualizedChainList.js.map +1 -1
  37. package/dist/esm/components/Header/NavigationHeader.js +5 -1
  38. package/dist/esm/components/Header/NavigationHeader.js.map +1 -1
  39. package/dist/esm/components/Messages/MinFromAmountUSDMessage.d.ts +6 -0
  40. package/dist/esm/components/Messages/MinFromAmountUSDMessage.js +15 -0
  41. package/dist/esm/components/Messages/MinFromAmountUSDMessage.js.map +1 -0
  42. package/dist/esm/components/Messages/WarningMessages.js +3 -0
  43. package/dist/esm/components/Messages/WarningMessages.js.map +1 -1
  44. package/dist/esm/components/Messages/useMessageQueue.js +15 -4
  45. package/dist/esm/components/Messages/useMessageQueue.js.map +1 -1
  46. package/dist/esm/components/Routes/RoutesContent.js +3 -2
  47. package/dist/esm/components/Routes/RoutesContent.js.map +1 -1
  48. package/dist/esm/components/Routes/RoutesExpanded.style.js +3 -2
  49. package/dist/esm/components/Routes/RoutesExpanded.style.js.map +1 -1
  50. package/dist/esm/components/SendToWallet/SendToWalletButton.js +1 -1
  51. package/dist/esm/components/SendToWallet/SendToWalletButton.js.map +1 -1
  52. package/dist/esm/components/SendToWallet/SendToWalletExpandButton.js +3 -3
  53. package/dist/esm/components/SendToWallet/SendToWalletExpandButton.js.map +1 -1
  54. package/dist/esm/components/Step/StepProcess.js +3 -8
  55. package/dist/esm/components/Step/StepProcess.js.map +1 -1
  56. package/dist/esm/components/TokenList/TokenDetailsSheet.js +0 -2
  57. package/dist/esm/components/TokenList/TokenDetailsSheet.js.map +1 -1
  58. package/dist/esm/components/TokenList/TokenDetailsSheetContent.js +27 -4
  59. package/dist/esm/components/TokenList/TokenDetailsSheetContent.js.map +1 -1
  60. package/dist/esm/components/TokenList/TokenList.js +10 -1
  61. package/dist/esm/components/TokenList/TokenList.js.map +1 -1
  62. package/dist/esm/components/TokenList/TokenListItem.js +3 -1
  63. package/dist/esm/components/TokenList/TokenListItem.js.map +1 -1
  64. package/dist/esm/components/TokenList/useTokenSelect.js +2 -4
  65. package/dist/esm/components/TokenList/useTokenSelect.js.map +1 -1
  66. package/dist/esm/config/version.d.ts +1 -1
  67. package/dist/esm/config/version.js +1 -1
  68. package/dist/esm/config/version.js.map +1 -1
  69. package/dist/esm/hooks/useFromAmountThreshold.d.ts +4 -0
  70. package/dist/esm/hooks/useFromAmountThreshold.js +23 -0
  71. package/dist/esm/hooks/useFromAmountThreshold.js.map +1 -0
  72. package/dist/esm/hooks/useGasSufficiency.js +3 -3
  73. package/dist/esm/hooks/useGasSufficiency.js.map +1 -1
  74. package/dist/esm/hooks/useLongPress.d.ts +7 -0
  75. package/dist/esm/hooks/useLongPress.js +41 -0
  76. package/dist/esm/hooks/useLongPress.js.map +1 -0
  77. package/dist/esm/hooks/useNavigateBack.d.ts +1 -1
  78. package/dist/esm/hooks/useNavigateBack.js +8 -2
  79. package/dist/esm/hooks/useNavigateBack.js.map +1 -1
  80. package/dist/esm/hooks/useRouteExecution.js +1 -2
  81. package/dist/esm/hooks/useRouteExecution.js.map +1 -1
  82. package/dist/esm/hooks/useRoutes.js +19 -4
  83. package/dist/esm/hooks/useRoutes.js.map +1 -1
  84. package/dist/esm/hooks/useSettingMonitor.js +1 -2
  85. package/dist/esm/hooks/useSettingMonitor.js.map +1 -1
  86. package/dist/esm/hooks/useTokenAddressBalance.js +4 -1
  87. package/dist/esm/hooks/useTokenAddressBalance.js.map +1 -1
  88. package/dist/esm/hooks/useTokenBalances.d.ts +1 -2
  89. package/dist/esm/hooks/useTokenBalances.js +2 -3
  90. package/dist/esm/hooks/useTokenBalances.js.map +1 -1
  91. package/dist/esm/hooks/useTokenSearch.d.ts +2 -2
  92. package/dist/esm/hooks/useTokenSearch.js +5 -2
  93. package/dist/esm/hooks/useTokenSearch.js.map +1 -1
  94. package/dist/esm/hooks/useTokens.d.ts +1 -2
  95. package/dist/esm/hooks/useTokens.js +3 -12
  96. package/dist/esm/hooks/useTokens.js.map +1 -1
  97. package/dist/esm/hooks/useTools.js +2 -2
  98. package/dist/esm/hooks/useTools.js.map +1 -1
  99. package/dist/esm/i18n/bn.json +5 -2
  100. package/dist/esm/i18n/de.json +5 -2
  101. package/dist/esm/i18n/en.json +5 -2
  102. package/dist/esm/i18n/es.json +5 -2
  103. package/dist/esm/i18n/fr.json +5 -2
  104. package/dist/esm/i18n/hi.json +5 -2
  105. package/dist/esm/i18n/id.json +5 -2
  106. package/dist/esm/i18n/it.json +5 -2
  107. package/dist/esm/i18n/ja.json +5 -2
  108. package/dist/esm/i18n/ko.json +5 -2
  109. package/dist/esm/i18n/pl.json +4 -1
  110. package/dist/esm/i18n/pt.json +5 -2
  111. package/dist/esm/i18n/th.json +5 -2
  112. package/dist/esm/i18n/tr.json +5 -2
  113. package/dist/esm/i18n/uk.json +5 -2
  114. package/dist/esm/i18n/vi.json +5 -2
  115. package/dist/esm/i18n/zh.json +5 -2
  116. package/dist/esm/pages/SelectEnabledToolsPage.js +4 -2
  117. package/dist/esm/pages/SelectEnabledToolsPage.js.map +1 -1
  118. package/dist/esm/pages/SelectTokenPage/SearchTokenInput.js +14 -7
  119. package/dist/esm/pages/SelectTokenPage/SearchTokenInput.js.map +1 -1
  120. package/dist/esm/pages/SettingsPage/BridgeAndExchangeSettings.js +1 -2
  121. package/dist/esm/pages/SettingsPage/BridgeAndExchangeSettings.js.map +1 -1
  122. package/dist/esm/pages/TransactionPage/TransactionPage.js +5 -3
  123. package/dist/esm/pages/TransactionPage/TransactionPage.js.map +1 -1
  124. package/dist/esm/stores/bookmarks/BookmarkStore.d.ts +1 -2
  125. package/dist/esm/stores/bookmarks/BookmarkStore.js +3 -3
  126. package/dist/esm/stores/bookmarks/BookmarkStore.js.map +1 -1
  127. package/dist/esm/stores/bookmarks/useBookmarkActions.js +1 -2
  128. package/dist/esm/stores/bookmarks/useBookmarkActions.js.map +1 -1
  129. package/dist/esm/stores/bookmarks/useBookmarks.js +1 -2
  130. package/dist/esm/stores/bookmarks/useBookmarks.js.map +1 -1
  131. package/dist/esm/stores/chains/ChainOrderStore.d.ts +1 -1
  132. package/dist/esm/stores/chains/ChainOrderStore.js +20 -7
  133. package/dist/esm/stores/chains/ChainOrderStore.js.map +1 -1
  134. package/dist/esm/stores/chains/createChainOrderStore.js +19 -1
  135. package/dist/esm/stores/chains/createChainOrderStore.js.map +1 -1
  136. package/dist/esm/stores/chains/types.d.ts +2 -0
  137. package/dist/esm/stores/chains/useChainOrder.js +1 -2
  138. package/dist/esm/stores/chains/useChainOrder.js.map +1 -1
  139. package/dist/esm/stores/form/useFieldActions.js +1 -2
  140. package/dist/esm/stores/form/useFieldActions.js.map +1 -1
  141. package/dist/esm/stores/form/useFieldValues.js +1 -2
  142. package/dist/esm/stores/form/useFieldValues.js.map +1 -1
  143. package/dist/esm/stores/form/useFormStore.d.ts +1 -2
  144. package/dist/esm/stores/form/useFormStore.js +3 -3
  145. package/dist/esm/stores/form/useFormStore.js.map +1 -1
  146. package/dist/esm/stores/form/useTouchedFields.js +1 -2
  147. package/dist/esm/stores/form/useTouchedFields.js.map +1 -1
  148. package/dist/esm/stores/form/useValidation.js +5 -2
  149. package/dist/esm/stores/form/useValidation.js.map +1 -1
  150. package/dist/esm/stores/form/useValidationActions.js +1 -2
  151. package/dist/esm/stores/form/useValidationActions.js.map +1 -1
  152. package/dist/esm/stores/header/useHeaderStore.d.ts +1 -1
  153. package/dist/esm/stores/header/useHeaderStore.js +5 -5
  154. package/dist/esm/stores/header/useHeaderStore.js.map +1 -1
  155. package/dist/esm/stores/inputMode/useInputModeStore.d.ts +9 -0
  156. package/dist/esm/stores/inputMode/useInputModeStore.js +19 -0
  157. package/dist/esm/stores/inputMode/useInputModeStore.js.map +1 -0
  158. package/dist/esm/stores/routes/RouteExecutionStore.d.ts +1 -1
  159. package/dist/esm/stores/routes/RouteExecutionStore.js +3 -2
  160. package/dist/esm/stores/routes/RouteExecutionStore.js.map +1 -1
  161. package/dist/esm/stores/routes/useExecutingRoutesIds.js +1 -2
  162. package/dist/esm/stores/routes/useExecutingRoutesIds.js.map +1 -1
  163. package/dist/esm/stores/settings/useSendToWalletStore.d.ts +2 -1
  164. package/dist/esm/stores/settings/useSendToWalletStore.js +6 -3
  165. package/dist/esm/stores/settings/useSendToWalletStore.js.map +1 -1
  166. package/dist/esm/stores/settings/useSettings.js +1 -2
  167. package/dist/esm/stores/settings/useSettings.js.map +1 -1
  168. package/dist/esm/stores/settings/useSettingsActions.js +1 -2
  169. package/dist/esm/stores/settings/useSettingsActions.js.map +1 -1
  170. package/dist/esm/stores/settings/useSettingsStore.d.ts +2 -1
  171. package/dist/esm/stores/settings/useSettingsStore.js +5 -1
  172. package/dist/esm/stores/settings/useSettingsStore.js.map +1 -1
  173. package/dist/esm/stores/settings/useSplitSubvariantStore.js +2 -1
  174. package/dist/esm/stores/settings/useSplitSubvariantStore.js.map +1 -1
  175. package/dist/esm/themes/createTheme.js +5 -1
  176. package/dist/esm/themes/createTheme.js.map +1 -1
  177. package/dist/esm/types/widget.d.ts +3 -1
  178. package/dist/esm/types/widget.js +1 -0
  179. package/dist/esm/types/widget.js.map +1 -1
  180. package/dist/esm/utils/format.d.ts +4 -0
  181. package/dist/esm/utils/format.js +26 -0
  182. package/dist/esm/utils/format.js.map +1 -1
  183. package/dist/esm/utils/getPriceImpact.d.ts +2 -2
  184. package/dist/esm/utils/getPriceImpact.js.map +1 -1
  185. package/package.json +12 -12
  186. package/package.json.tmp +14 -14
  187. package/src/components/AmountInput/AmountInput.style.tsx +13 -0
  188. package/src/components/AmountInput/AmountInput.tsx +91 -18
  189. package/src/components/AmountInput/AmountInputAdornment.style.tsx +66 -5
  190. package/src/components/AmountInput/AmountInputEndAdornment.tsx +40 -10
  191. package/src/components/AmountInput/PriceFormHelperText.style.tsx +29 -0
  192. package/src/components/AmountInput/PriceFormHelperText.tsx +63 -19
  193. package/src/components/AppContainer.tsx +1 -0
  194. package/src/components/ChainSelect/useChainSelect.ts +2 -4
  195. package/src/components/Chains/ChainList.style.tsx +10 -2
  196. package/src/components/Chains/ChainList.tsx +6 -5
  197. package/src/components/Chains/ChainListItem.tsx +29 -0
  198. package/src/components/Chains/PinChainButton.tsx +51 -0
  199. package/src/components/Chains/SelectChainContent.tsx +18 -21
  200. package/src/components/Chains/VirtualizedChainList.tsx +67 -20
  201. package/src/components/Header/NavigationHeader.tsx +10 -1
  202. package/src/components/Messages/MinFromAmountUSDMessage.tsx +35 -0
  203. package/src/components/Messages/WarningMessages.tsx +8 -0
  204. package/src/components/Messages/useMessageQueue.ts +16 -4
  205. package/src/components/Routes/RoutesContent.tsx +6 -2
  206. package/src/components/Routes/RoutesExpanded.style.ts +3 -2
  207. package/src/components/SendToWallet/SendToWalletButton.tsx +1 -1
  208. package/src/components/SendToWallet/SendToWalletExpandButton.tsx +8 -3
  209. package/src/components/Step/StepProcess.tsx +3 -8
  210. package/src/components/TokenList/TokenDetailsSheet.tsx +0 -2
  211. package/src/components/TokenList/TokenDetailsSheetContent.tsx +50 -3
  212. package/src/components/TokenList/TokenList.tsx +24 -1
  213. package/src/components/TokenList/TokenListItem.tsx +6 -0
  214. package/src/components/TokenList/useTokenSelect.ts +2 -4
  215. package/src/config/version.ts +1 -1
  216. package/src/hooks/useFromAmountThreshold.ts +35 -0
  217. package/src/hooks/useGasSufficiency.ts +4 -4
  218. package/src/hooks/useLongPress.ts +51 -0
  219. package/src/hooks/useNavigateBack.ts +26 -17
  220. package/src/hooks/useRouteExecution.ts +1 -3
  221. package/src/hooks/useRoutes.ts +24 -4
  222. package/src/hooks/useSettingMonitor.ts +7 -11
  223. package/src/hooks/useTokenAddressBalance.ts +6 -1
  224. package/src/hooks/useTokenBalances.ts +3 -10
  225. package/src/hooks/useTokenSearch.ts +7 -4
  226. package/src/hooks/useTokens.ts +4 -31
  227. package/src/hooks/useTools.ts +2 -2
  228. package/src/i18n/bn.json +5 -2
  229. package/src/i18n/de.json +5 -2
  230. package/src/i18n/en.json +5 -2
  231. package/src/i18n/es.json +5 -2
  232. package/src/i18n/fr.json +5 -2
  233. package/src/i18n/hi.json +5 -2
  234. package/src/i18n/id.json +5 -2
  235. package/src/i18n/it.json +5 -2
  236. package/src/i18n/ja.json +5 -2
  237. package/src/i18n/ko.json +5 -2
  238. package/src/i18n/pl.json +4 -1
  239. package/src/i18n/pt.json +5 -2
  240. package/src/i18n/th.json +5 -2
  241. package/src/i18n/tr.json +5 -2
  242. package/src/i18n/uk.json +5 -2
  243. package/src/i18n/vi.json +5 -2
  244. package/src/i18n/zh.json +5 -2
  245. package/src/pages/SelectEnabledToolsPage.tsx +4 -5
  246. package/src/pages/SelectTokenPage/SearchTokenInput.tsx +19 -7
  247. package/src/pages/SettingsPage/BridgeAndExchangeSettings.tsx +1 -2
  248. package/src/pages/TransactionPage/TransactionPage.tsx +17 -9
  249. package/src/stores/bookmarks/BookmarkStore.tsx +3 -6
  250. package/src/stores/bookmarks/useBookmarkActions.ts +9 -13
  251. package/src/stores/bookmarks/useBookmarks.ts +1 -3
  252. package/src/stores/chains/ChainOrderStore.tsx +26 -8
  253. package/src/stores/chains/createChainOrderStore.ts +19 -1
  254. package/src/stores/chains/types.ts +2 -0
  255. package/src/stores/chains/useChainOrder.ts +1 -5
  256. package/src/stores/form/useFieldActions.ts +10 -14
  257. package/src/stores/form/useFieldValues.ts +1 -3
  258. package/src/stores/form/useFormStore.ts +3 -6
  259. package/src/stores/form/useTouchedFields.ts +1 -2
  260. package/src/stores/form/useValidation.ts +5 -5
  261. package/src/stores/form/useValidationActions.ts +5 -9
  262. package/src/stores/header/useHeaderStore.tsx +5 -14
  263. package/src/stores/inputMode/useInputModeStore.ts +29 -0
  264. package/src/stores/routes/RouteExecutionStore.tsx +3 -3
  265. package/src/stores/routes/useExecutingRoutesIds.ts +14 -17
  266. package/src/stores/settings/useSendToWalletStore.ts +11 -8
  267. package/src/stores/settings/useSettings.ts +8 -11
  268. package/src/stores/settings/useSettingsActions.ts +8 -12
  269. package/src/stores/settings/useSettingsStore.ts +8 -1
  270. package/src/stores/settings/useSplitSubvariantStore.tsx +2 -1
  271. package/src/themes/createTheme.ts +5 -1
  272. package/src/types/widget.ts +2 -0
  273. package/src/utils/format.ts +33 -0
  274. package/src/utils/getPriceImpact.ts +2 -2
@@ -8,7 +8,7 @@ import type { FormTypeProps } from '../../stores/form/types.js'
8
8
  import { FormKeyHelper } from '../../stores/form/types.js'
9
9
  import { useFieldActions } from '../../stores/form/useFieldActions.js'
10
10
  import { useFieldValues } from '../../stores/form/useFieldValues.js'
11
- import { MaxButton, MaxButtonSkeleton } from './AmountInputAdornment.style.js'
11
+ import { ButtonContainer, MaxButton } from './AmountInputAdornment.style.js'
12
12
 
13
13
  export const AmountInputEndAdornment = ({ formType }: FormTypeProps) => {
14
14
  const { t } = useTranslation()
@@ -24,11 +24,11 @@ export const AmountInputEndAdornment = ({ formType }: FormTypeProps) => {
24
24
  // the user will have enough funds remaining to cover gas costs
25
25
  const { data } = useGasRecommendation(chainId)
26
26
 
27
- const { token, isLoading } = useTokenAddressBalance(chainId, tokenAddress)
27
+ const { token } = useTokenAddressBalance(chainId, tokenAddress)
28
28
 
29
- const handleMax = () => {
29
+ const getMaxAmount = () => {
30
30
  if (!token?.amount) {
31
- return
31
+ return 0n
32
32
  }
33
33
  const chain = getChainById(chainId)
34
34
  let maxAmount = token.amount
@@ -38,7 +38,26 @@ export const AmountInputEndAdornment = ({ formType }: FormTypeProps) => {
38
38
  maxAmount = token.amount - recommendedAmount
39
39
  }
40
40
  }
41
- if (maxAmount) {
41
+ return maxAmount
42
+ }
43
+
44
+ const handlePercentage = (percentage: number) => {
45
+ const maxAmount = getMaxAmount()
46
+ if (maxAmount && token?.decimals) {
47
+ const percentageAmount = (maxAmount * BigInt(percentage)) / 100n
48
+ setFieldValue(
49
+ FormKeyHelper.getAmountKey(formType),
50
+ formatUnits(percentageAmount, token.decimals),
51
+ {
52
+ isTouched: true,
53
+ }
54
+ )
55
+ }
56
+ }
57
+
58
+ const handleMax = () => {
59
+ const maxAmount = getMaxAmount()
60
+ if (maxAmount && token?.decimals) {
42
61
  setFieldValue(
43
62
  FormKeyHelper.getAmountKey(formType),
44
63
  formatUnits(maxAmount, token.decimals),
@@ -50,11 +69,22 @@ export const AmountInputEndAdornment = ({ formType }: FormTypeProps) => {
50
69
  }
51
70
 
52
71
  return (
53
- <InputAdornment position="end">
54
- {isLoading && tokenAddress ? (
55
- <MaxButtonSkeleton variant="rectangular" />
56
- ) : formType === 'from' && token?.amount ? (
57
- <MaxButton onClick={handleMax}>{t('button.max')}</MaxButton>
72
+ <InputAdornment position="end" sx={{ paddingTop: 2 }}>
73
+ {formType === 'from' && token?.amount ? (
74
+ <ButtonContainer>
75
+ <MaxButton onClick={() => handlePercentage(25)} data-delay="0">
76
+ 25%
77
+ </MaxButton>
78
+ <MaxButton onClick={() => handlePercentage(50)} data-delay="1">
79
+ 50%
80
+ </MaxButton>
81
+ <MaxButton onClick={() => handlePercentage(75)} data-delay="2">
82
+ 75%
83
+ </MaxButton>
84
+ <MaxButton onClick={handleMax} data-delay="3">
85
+ {t('button.max')}
86
+ </MaxButton>
87
+ </ButtonContainer>
58
88
  ) : null}
59
89
  </InputAdornment>
60
90
  )
@@ -0,0 +1,29 @@
1
+ import { Button, styled } from '@mui/material'
2
+
3
+ export const InputPriceButton = styled(Button)(({ theme, onClick }) => ({
4
+ color: theme.vars.palette.text.secondary,
5
+ padding: theme.spacing(0.25, 0.5, 0.25, 0.75),
6
+ maxHeight: 16,
7
+ fontSize: '0.75rem',
8
+ fontWeight: 500,
9
+ borderRadius: `calc(${theme.vars.shape.borderRadius} * 2)`,
10
+ backgroundColor: 'transparent',
11
+ minWidth: 32,
12
+ ...(onClick
13
+ ? {
14
+ '&:hover': {
15
+ backgroundColor: `rgba(${theme.vars.palette.common.onBackgroundChannel} / 0.04)`,
16
+ },
17
+ ...theme.applyStyles('dark', {
18
+ backgroundColor: 'transparent',
19
+ '&:hover': {
20
+ backgroundColor: `rgba(${theme.vars.palette.common.onBackgroundChannel} / 0.04)`,
21
+ },
22
+ }),
23
+ }
24
+ : {
25
+ cursor: 'text',
26
+ userSelect: 'text',
27
+ pointerEvents: 'none',
28
+ }),
29
+ }))
@@ -1,11 +1,14 @@
1
1
  import type { TokenAmount } from '@lifi/sdk'
2
+ import SwapVertIcon from '@mui/icons-material/SwapVert'
2
3
  import { FormHelperText, Skeleton, Typography } from '@mui/material'
3
4
  import { useTranslation } from 'react-i18next'
4
5
  import { useTokenAddressBalance } from '../../hooks/useTokenAddressBalance.js'
5
6
  import type { FormTypeProps } from '../../stores/form/types.js'
6
7
  import { FormKeyHelper } from '../../stores/form/types.js'
7
8
  import { useFieldValues } from '../../stores/form/useFieldValues.js'
9
+ import { useInputModeStore } from '../../stores/inputMode/useInputModeStore.js'
8
10
  import { formatTokenAmount, formatTokenPrice } from '../../utils/format.js'
11
+ import { InputPriceButton } from './PriceFormHelperText.style.js'
9
12
 
10
13
  export const PriceFormHelperText: React.FC<FormTypeProps> = ({ formType }) => {
11
14
  const [chainId, tokenAddress] = useFieldValues(
@@ -33,11 +36,31 @@ export const PriceFormHelperTextBase: React.FC<
33
36
  > = ({ formType, isLoading, tokenAddress, token }) => {
34
37
  const { t } = useTranslation()
35
38
  const [amount] = useFieldValues(FormKeyHelper.getAmountKey(formType))
39
+ const { inputMode, toggleInputMode } = useInputModeStore()
40
+
41
+ const currentInputMode = inputMode[formType]
36
42
 
37
43
  const tokenAmount = token
38
44
  ? formatTokenAmount(token.amount, token.decimals)
39
45
  : '0'
40
- const tokenPrice = formatTokenPrice(amount, token?.priceUSD)
46
+
47
+ const getPriceAmountDisplayValue = () => {
48
+ if (currentInputMode === 'amount') {
49
+ const tokenPrice = formatTokenPrice(
50
+ amount,
51
+ token?.priceUSD,
52
+ token?.decimals
53
+ )
54
+ return t('format.currency', { value: tokenPrice })
55
+ } else {
56
+ return t('format.tokenAmount', { value: amount || '0' })
57
+ }
58
+ }
59
+
60
+ const handleToggleMode = (e: React.MouseEvent) => {
61
+ e.stopPropagation()
62
+ toggleInputMode(formType)
63
+ }
41
64
 
42
65
  return (
43
66
  <FormHelperText
@@ -45,28 +68,49 @@ export const PriceFormHelperTextBase: React.FC<
45
68
  sx={{
46
69
  display: 'flex',
47
70
  justifyContent: 'space-between',
71
+ alignItems: 'center',
48
72
  margin: 0,
49
- marginLeft: 2,
50
- marginTop: 0.75,
73
+ marginLeft: 1.25,
74
+ marginTop: 0.5,
51
75
  }}
52
76
  >
53
- <Typography
54
- sx={{
55
- color: 'text.secondary',
56
- fontWeight: 500,
57
- fontSize: 12,
58
- lineHeight: 1,
59
- flex: 1,
60
- wordBreak: 'break-word',
61
- overflowWrap: 'break-word',
62
- }}
77
+ <InputPriceButton
78
+ onClick={token?.priceUSD ? handleToggleMode : undefined}
63
79
  >
64
- {t('format.currency', {
65
- value: tokenPrice,
66
- })}
67
- </Typography>
80
+ <Typography
81
+ sx={{
82
+ color: 'text.secondary',
83
+ fontWeight: 500,
84
+ fontSize: 12,
85
+ lineHeight: 1,
86
+ marginRight: 0.25,
87
+ maxWidth: 136,
88
+ overflow: 'hidden',
89
+ textOverflow: 'ellipsis',
90
+ whiteSpace: 'nowrap',
91
+ }}
92
+ >
93
+ {getPriceAmountDisplayValue()}
94
+ </Typography>
95
+ {currentInputMode === 'price' && token?.symbol ? (
96
+ <Typography
97
+ sx={{
98
+ color: 'text.secondary',
99
+ fontWeight: 500,
100
+ fontSize: 12,
101
+ lineHeight: 1,
102
+ wordBreak: 'break-word',
103
+ overflowWrap: 'break-word',
104
+ marginRight: 0.25,
105
+ }}
106
+ >
107
+ {token.symbol}
108
+ </Typography>
109
+ ) : null}
110
+ {token?.priceUSD && <SwapVertIcon sx={{ fontSize: 14 }} />}
111
+ </InputPriceButton>
68
112
  {isLoading && tokenAddress ? (
69
- <Skeleton variant="text" width={48} height={12} />
113
+ <Skeleton variant="text" width={56} height={16} />
70
114
  ) : token?.amount ? (
71
115
  <Typography
72
116
  sx={{
@@ -74,7 +118,7 @@ export const PriceFormHelperTextBase: React.FC<
74
118
  fontSize: 12,
75
119
  color: 'text.secondary',
76
120
  lineHeight: 1,
77
- pl: 0.25,
121
+ paddingLeft: 0.25,
78
122
  }}
79
123
  title={tokenAmount}
80
124
  >
@@ -71,6 +71,7 @@ export const RelativeContainer = styled(Box, {
71
71
  maxHeight: 'none',
72
72
  height: '100%',
73
73
  boxShadow: 'none',
74
+ borderRadius: 0,
74
75
  },
75
76
  },
76
77
  ],
@@ -9,13 +9,11 @@ import { useChainOrder } from '../../stores/chains/useChainOrder.js'
9
9
  import type { FormType } from '../../stores/form/types.js'
10
10
  import { FormKeyHelper } from '../../stores/form/types.js'
11
11
  import { useFieldActions } from '../../stores/form/useFieldActions.js'
12
- import { useFieldController } from '../../stores/form/useFieldController.js'
13
12
  import type { DisabledUI } from '../../types/widget.js'
14
13
 
15
14
  export const useChainSelect = (formType: FormType) => {
16
15
  const { disabledUI } = useWidgetConfig()
17
16
  const chainKey = FormKeyHelper.getChainKey(formType)
18
- const { onChange } = useFieldController({ name: chainKey })
19
17
  const { setFieldValue, getFieldValues } = useFieldActions()
20
18
  const { useExternalWalletProvidersOnly, externalChainTypes } =
21
19
  useExternalWalletProvider()
@@ -45,7 +43,7 @@ export const useChainSelect = (formType: FormType) => {
45
43
 
46
44
  const setCurrentChain = useCallback(
47
45
  (chainId: number) => {
48
- onChange(chainId)
46
+ setFieldValue(chainKey, chainId, { isDirty: true, isTouched: true })
49
47
  if (swapOnly) {
50
48
  setFieldValue(FormKeyHelper.getChainKey('to'), chainId, {
51
49
  isTouched: true,
@@ -69,7 +67,7 @@ export const useChainSelect = (formType: FormType) => {
69
67
  setChainOrder(chainId, formType)
70
68
  },
71
69
  [
72
- onChange,
70
+ chainKey,
73
71
  swapOnly,
74
72
  setFieldValue,
75
73
  disabledUI,
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  ListItem as ListItemBase,
3
+ listItemButtonClasses,
3
4
  listItemTextClasses,
4
5
  Avatar as MuiAvatar,
5
6
  List as MuiList,
@@ -7,7 +8,7 @@ import {
7
8
  ListItemText as MuiListItemText,
8
9
  styled,
9
10
  } from '@mui/material'
10
- import { ListItemButton as ListItemButtonBase } from '../ListItem/ListItemButton.js'
11
+ import { ListItemButton as ListItemButtonBase } from '../ListItemButton.js'
11
12
 
12
13
  export const Avatar = styled(MuiAvatar)<{
13
14
  size?: 'small' | 'medium'
@@ -28,6 +29,9 @@ export const ListItemText = styled(MuiListItemText)<{
28
29
  [`.${listItemTextClasses.primary}`]: {
29
30
  fontWeight: 500,
30
31
  fontSize: size === 'small' ? '1rem' : '1.125rem',
32
+ textOverflow: 'ellipsis',
33
+ overflow: 'hidden',
34
+ whiteSpace: 'nowrap',
31
35
  },
32
36
  }))
33
37
 
@@ -38,6 +42,7 @@ export const ListItemButton = styled(ListItemButtonBase)<{
38
42
  borderRadius: theme.vars.shape.borderRadius,
39
43
  paddingLeft: size === 'small' ? theme.spacing(1) : theme.spacing(1.5),
40
44
  height: size === 'small' ? 44 : 56,
45
+ width: '100%',
41
46
  }
42
47
  })
43
48
 
@@ -52,8 +57,11 @@ export const List = styled(MuiList)(({ theme }) => ({
52
57
  cursor: 'pointer',
53
58
  }))
54
59
 
55
- export const ListItem = styled(ListItemBase)(() => ({
60
+ export const ListItem = styled(ListItemBase)(({ theme }) => ({
56
61
  position: 'absolute',
57
62
  top: 0,
58
63
  left: 0,
64
+ [`& .${listItemButtonClasses.root}`]: {
65
+ paddingRight: theme.spacing(1),
66
+ },
59
67
  }))
@@ -17,8 +17,7 @@ interface ChainListProps {
17
17
  onSelect: (chain: ExtendedChain) => void
18
18
  selectedChainId?: number
19
19
  isLoading: boolean
20
- itemsSize: 'small' | 'medium'
21
- adjustForStickySearchInput?: boolean
20
+ inExpansion: boolean
22
21
  }
23
22
 
24
23
  export const ChainList = ({
@@ -27,11 +26,12 @@ export const ChainList = ({
27
26
  onSelect,
28
27
  selectedChainId,
29
28
  isLoading,
30
- itemsSize,
31
- adjustForStickySearchInput,
29
+ inExpansion,
32
30
  }: ChainListProps) => {
33
31
  const { t } = useTranslation()
34
32
 
33
+ const itemsSize = inExpansion ? 'small' : 'medium'
34
+
35
35
  if (isLoading) {
36
36
  return (
37
37
  <List disablePadding sx={{ cursor: 'default' }}>
@@ -69,7 +69,7 @@ export const ChainList = ({
69
69
  return (
70
70
  <SearchNotFound
71
71
  message={t('info.message.emptyChainList')}
72
- adjustForStickySearchInput={adjustForStickySearchInput}
72
+ adjustForStickySearchInput={!inExpansion}
73
73
  />
74
74
  )
75
75
  }
@@ -81,6 +81,7 @@ export const ChainList = ({
81
81
  onSelect={onSelect}
82
82
  selectedChainId={selectedChainId}
83
83
  itemsSize={itemsSize}
84
+ withPinnedChains={inExpansion}
84
85
  />
85
86
  )
86
87
  }
@@ -1,4 +1,5 @@
1
1
  import type { ExtendedChain } from '@lifi/sdk'
2
+ import { Box } from '@mui/material'
2
3
  import { memo } from 'react'
3
4
  import {
4
5
  Avatar,
@@ -7,6 +8,7 @@ import {
7
8
  ListItemButton,
8
9
  ListItemText,
9
10
  } from './ChainList.style'
11
+ import { PinChainButton, pinButtonClassName } from './PinChainButton'
10
12
 
11
13
  interface ChainListItemProps {
12
14
  chain: ExtendedChain
@@ -15,6 +17,9 @@ interface ChainListItemProps {
15
17
  itemsSize: 'small' | 'medium'
16
18
  size: number
17
19
  start: number
20
+ isPinned: boolean
21
+ onPin: (chainId: number) => void
22
+ withPin: boolean
18
23
  }
19
24
 
20
25
  export const ChainListItem = memo(
@@ -25,6 +30,9 @@ export const ChainListItem = memo(
25
30
  itemsSize,
26
31
  size,
27
32
  start,
33
+ isPinned,
34
+ onPin,
35
+ withPin,
28
36
  }: ChainListItemProps) => {
29
37
  return (
30
38
  <ListItem
@@ -32,6 +40,13 @@ export const ChainListItem = memo(
32
40
  height: `${size}px`,
33
41
  transform: `translateY(${start}px)`,
34
42
  padding: 0,
43
+ overflow: 'hidden',
44
+ }}
45
+ sx={{
46
+ [`&:hover .${pinButtonClassName}`]: {
47
+ opacity: 1,
48
+ transform: 'translateY(0)',
49
+ },
35
50
  }}
36
51
  >
37
52
  <ListItemButton
@@ -45,6 +60,20 @@ export const ChainListItem = memo(
45
60
  </Avatar>
46
61
  </ListItemAvatar>
47
62
  <ListItemText primary={chain.name} size={itemsSize} />
63
+ {withPin && (
64
+ <Box
65
+ style={{
66
+ position: 'relative',
67
+ height: 28,
68
+ width: 28,
69
+ }}
70
+ >
71
+ <PinChainButton
72
+ isPinned={isPinned}
73
+ onPin={() => onPin(chain.id)}
74
+ />
75
+ </Box>
76
+ )}
48
77
  </ListItemButton>
49
78
  </ListItem>
50
79
  )
@@ -0,0 +1,51 @@
1
+ import PushPinIcon from '@mui/icons-material/PushPin'
2
+ import PushPinOutlinedIcon from '@mui/icons-material/PushPinOutlined'
3
+ import { IconButton } from '@mui/material'
4
+
5
+ interface PinChainButtonProps {
6
+ isPinned: boolean
7
+ onPin: () => void
8
+ }
9
+
10
+ export const pinButtonClassName = 'pin-button'
11
+ const animationDuration = 225
12
+
13
+ export const PinChainButton = ({ isPinned, onPin }: PinChainButtonProps) => {
14
+ const PinIcon = isPinned ? PushPinIcon : PushPinOutlinedIcon
15
+ return (
16
+ <IconButton
17
+ className={pinButtonClassName}
18
+ edge="end"
19
+ aria-label="pin"
20
+ onClick={(e) => {
21
+ e.stopPropagation()
22
+ onPin()
23
+ }}
24
+ sx={{
25
+ position: 'absolute',
26
+ top: 0,
27
+ left: 0,
28
+ height: 28,
29
+ width: 28,
30
+ transition: `opacity ${animationDuration}ms cubic-bezier(0.4, 0, 0.2, 1), transform ${animationDuration}ms cubic-bezier(0.4, 0, 0.2, 1)`,
31
+ ...(isPinned
32
+ ? {
33
+ opacity: 1,
34
+ transform: 'translateY(0)',
35
+ }
36
+ : {
37
+ opacity: 0,
38
+ transform: 'translateY(-50%)',
39
+ }),
40
+ }}
41
+ >
42
+ <PinIcon
43
+ sx={{
44
+ height: 20,
45
+ width: 20,
46
+ color: 'text.secondary',
47
+ }}
48
+ />
49
+ </IconButton>
50
+ )
51
+ }
@@ -30,31 +30,28 @@ export const SelectChainContent: React.FC<SelectChainContentProps> = memo(
30
30
  )
31
31
  const inputRef = useRef<HTMLInputElement>(null)
32
32
  const listRef = useRef<HTMLDivElement>(null)
33
- const [filteredChains, setFilteredChains] = useState<ExtendedChain[]>(
34
- chains ?? []
35
- )
33
+ const [debouncedSearchValue, setDebouncedSearchValue] = useState('')
34
+
35
+ const filteredChains = useMemo(() => {
36
+ const value = debouncedSearchValue.toLowerCase()
37
+ return value
38
+ ? (chains?.filter((chain) =>
39
+ chain.name.toLowerCase().includes(value)
40
+ ) ?? [])
41
+ : (chains ?? [])
42
+ }, [chains, debouncedSearchValue])
36
43
 
37
44
  const scrollToTop = useCallback(() => {
38
45
  // Scroll widget container to top
39
46
  if (!inExpansion && scrollableContainer) {
40
47
  scrollableContainer.scrollTop = 0
41
48
  }
42
- // Scroll chain list to top
43
- if (inExpansion && listRef.current) {
44
- listRef.current.scrollTop = 0
45
- }
46
49
  }, [inExpansion, scrollableContainer])
47
50
 
48
51
  const debouncedFilterChains = useMemo(
49
52
  () =>
50
- debounce((chains: ExtendedChain[]) => {
51
- const value = inputRef.current?.value?.toLowerCase() || ''
52
- const filtered = value
53
- ? chains?.filter((chain) =>
54
- chain.name.toLowerCase().includes(value)
55
- )
56
- : chains
57
- setFilteredChains(filtered ?? [])
53
+ debounce((value: string) => {
54
+ setDebouncedSearchValue(value)
58
55
  scrollToTop()
59
56
  }, 250),
60
57
  [scrollToTop]
@@ -68,13 +65,14 @@ export const SelectChainContent: React.FC<SelectChainContentProps> = memo(
68
65
  )
69
66
 
70
67
  const onChange = useCallback(() => {
71
- debouncedFilterChains(chains ?? [])
72
- }, [chains, debouncedFilterChains])
68
+ const value = inputRef.current?.value || ''
69
+ debouncedFilterChains(value)
70
+ }, [debouncedFilterChains])
73
71
 
74
72
  const onClear = useCallback(() => {
75
- setFilteredChains(chains ?? [])
73
+ setDebouncedSearchValue('')
76
74
  scrollToTop()
77
- }, [chains, scrollToTop])
75
+ }, [scrollToTop])
78
76
 
79
77
  const listContainerHeight = useMemo(() => {
80
78
  const fullContainerHeight = getWidgetMaxHeight(theme)
@@ -106,8 +104,7 @@ export const SelectChainContent: React.FC<SelectChainContentProps> = memo(
106
104
  isLoading={isLoading}
107
105
  onSelect={onSelect ?? onSelectChainFallback}
108
106
  selectedChainId={selectedChainId}
109
- itemsSize={inExpansion ? 'small' : 'medium'}
110
- adjustForStickySearchInput={!inExpansion}
107
+ inExpansion={inExpansion}
111
108
  />
112
109
  </Box>
113
110
  </FullPageContainer>
@@ -1,7 +1,8 @@
1
1
  import type { ExtendedChain } from '@lifi/sdk'
2
2
  import { useVirtualizer } from '@tanstack/react-virtual'
3
3
  import type { RefObject } from 'react'
4
- import { useCallback, useEffect, useMemo, useRef } from 'react'
4
+ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef } from 'react'
5
+ import { useChainOrderStore } from '../../stores/chains/ChainOrderStore'
5
6
  import { List } from './ChainList.style'
6
7
  import { ChainListItem } from './ChainListItem'
7
8
 
@@ -11,6 +12,7 @@ interface VirtualizedChainListProps {
11
12
  onSelect: (chain: ExtendedChain) => void
12
13
  selectedChainId?: number
13
14
  itemsSize: 'small' | 'medium'
15
+ withPinnedChains: boolean
14
16
  }
15
17
 
16
18
  export const VirtualizedChainList = ({
@@ -19,17 +21,33 @@ export const VirtualizedChainList = ({
19
21
  selectedChainId,
20
22
  itemsSize,
21
23
  scrollElementRef,
24
+ withPinnedChains,
22
25
  }: VirtualizedChainListProps) => {
23
- const initialSelectedChainIdRef = useRef(selectedChainId)
26
+ const selectedChainIdRef = useRef(selectedChainId) // Store the initial selected chain ID to scroll to it once chains are loaded
27
+ const hasScrolledRef = useRef(false)
28
+ const [pinnedChains, setPinnedChain] = useChainOrderStore((state) => [
29
+ state.pinnedChains,
30
+ state.setPinnedChain,
31
+ ])
32
+ const onPin = useCallback(
33
+ (chainId: number) => {
34
+ setPinnedChain(chainId)
35
+ },
36
+ [setPinnedChain]
37
+ )
38
+
24
39
  const sortedChains = useMemo(() => {
25
- const selectedChain = chains.find(
26
- (chain) => chain.id === initialSelectedChainIdRef.current
27
- )
28
- const otherChains = chains.filter(
29
- (chain) => chain.id !== initialSelectedChainIdRef.current
30
- )
31
- return selectedChain ? [selectedChain, ...otherChains] : chains
32
- }, [chains])
40
+ if (!pinnedChains.length) {
41
+ return chains
42
+ }
43
+ // Pinning logic: move pinned chains to the top of the list
44
+ const pinned = pinnedChains
45
+ .map((id) => chains.find((c) => c.id === id))
46
+ .filter(Boolean) as ExtendedChain[]
47
+ const pinnedIds = new Set(pinned.map((c) => c.id))
48
+ const rest = chains.filter((c) => !pinnedIds.has(c.id))
49
+ return [...pinned, ...rest]
50
+ }, [chains, pinnedChains])
33
51
 
34
52
  const getItemKey = useCallback(
35
53
  (index: number) => {
@@ -38,16 +56,17 @@ export const VirtualizedChainList = ({
38
56
  [sortedChains]
39
57
  )
40
58
 
41
- const { getVirtualItems, getTotalSize, measure } = useVirtualizer({
42
- count: sortedChains.length,
43
- overscan: 3,
44
- paddingEnd: 0,
45
- getScrollElement: () => scrollElementRef.current,
46
- estimateSize: () => {
47
- return itemsSize === 'small' ? 48 : 60
48
- },
49
- getItemKey,
50
- })
59
+ const { getVirtualItems, getTotalSize, measure, scrollToIndex, range } =
60
+ useVirtualizer({
61
+ count: sortedChains.length,
62
+ overscan: 3,
63
+ paddingEnd: 0,
64
+ getScrollElement: () => scrollElementRef.current,
65
+ estimateSize: () => {
66
+ return itemsSize === 'small' ? 48 : 60
67
+ },
68
+ getItemKey,
69
+ })
51
70
 
52
71
  // Using mountOnEnter of the ExpansionTransition component
53
72
  // leads to a short delay for setting up scrollElementRef,
@@ -59,6 +78,31 @@ export const VirtualizedChainList = ({
59
78
  }
60
79
  }, [measure, scrollElementRef.current])
61
80
 
81
+ useLayoutEffect(() => {
82
+ // Only scroll if sortedChains is not empty and we haven't scrolled yet
83
+ if (!hasScrolledRef.current && sortedChains.length > 0 && range) {
84
+ const selectedChainIndex = sortedChains.findIndex(
85
+ (chain) => chain.id === selectedChainIdRef.current
86
+ )
87
+ if (selectedChainIndex !== -1) {
88
+ // Only scroll if the selected chain is not in the visible range
89
+ // +1 and -1 to account for partially visible items
90
+ if (
91
+ range.startIndex + 1 > selectedChainIndex ||
92
+ range.endIndex - 1 < selectedChainIndex
93
+ ) {
94
+ requestAnimationFrame(() => {
95
+ scrollToIndex(selectedChainIndex, {
96
+ align: 'center',
97
+ behavior: 'smooth',
98
+ })
99
+ })
100
+ }
101
+ }
102
+ hasScrolledRef.current = true // Mark as scrolled (when needed)
103
+ }
104
+ }, [sortedChains, scrollToIndex, range])
105
+
62
106
  return (
63
107
  <List
64
108
  className="long-list"
@@ -76,6 +120,9 @@ export const VirtualizedChainList = ({
76
120
  itemsSize={itemsSize}
77
121
  size={item.size}
78
122
  start={item.start}
123
+ withPin={withPinnedChains}
124
+ isPinned={pinnedChains.includes(chain.id)}
125
+ onPin={onPin}
79
126
  />
80
127
  )
81
128
  })}