@lifi/widget 4.1.0 → 4.2.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 (204) hide show
  1. package/dist/esm/AppDefault.js +14 -0
  2. package/dist/esm/AppDefault.js.map +1 -1
  3. package/dist/esm/components/AmountInput/AmountInput.js +2 -1
  4. package/dist/esm/components/AmountInput/AmountInput.js.map +1 -1
  5. package/dist/esm/components/AmountInput/AmountInputEndAdornment.js +4 -2
  6. package/dist/esm/components/AmountInput/AmountInputEndAdornment.js.map +1 -1
  7. package/dist/esm/components/AmountInput/AmountInputEndAdornment.style.js +1 -1
  8. package/dist/esm/components/AmountInput/AmountInputEndAdornment.style.js.map +1 -1
  9. package/dist/esm/components/AmountInput/PriceFormHelperText.js +4 -2
  10. package/dist/esm/components/AmountInput/PriceFormHelperText.js.map +1 -1
  11. package/dist/esm/components/AmountInputCard/AmountInputCard.style.js +98 -0
  12. package/dist/esm/components/AmountInputCard/AmountInputCard.style.js.map +1 -0
  13. package/dist/esm/components/AmountInputCard/AmountInputCardPair.js +25 -0
  14. package/dist/esm/components/AmountInputCard/AmountInputCardPair.js.map +1 -0
  15. package/dist/esm/components/AmountInputCard/BalanceDisplay.js +35 -0
  16. package/dist/esm/components/AmountInputCard/BalanceDisplay.js.map +1 -0
  17. package/dist/esm/components/AmountInputCard/BalanceDisplay.style.js +11 -0
  18. package/dist/esm/components/AmountInputCard/BalanceDisplay.style.js.map +1 -0
  19. package/dist/esm/components/AmountInputCard/FiatValueToggle.js +41 -0
  20. package/dist/esm/components/AmountInputCard/FiatValueToggle.js.map +1 -0
  21. package/dist/esm/components/AmountInputCard/PercentageChips.js +80 -0
  22. package/dist/esm/components/AmountInputCard/PercentageChips.js.map +1 -0
  23. package/dist/esm/components/AmountInputCard/PercentageChips.style.js +43 -0
  24. package/dist/esm/components/AmountInputCard/PercentageChips.style.js.map +1 -0
  25. package/dist/esm/components/AmountInputCard/ReceiveAmountCard.js +137 -0
  26. package/dist/esm/components/AmountInputCard/ReceiveAmountCard.js.map +1 -0
  27. package/dist/esm/components/AmountInputCard/SendAmountCard.js +102 -0
  28. package/dist/esm/components/AmountInputCard/SendAmountCard.js.map +1 -0
  29. package/dist/esm/components/Avatar/Avatar.js +12 -3
  30. package/dist/esm/components/Avatar/Avatar.js.map +1 -1
  31. package/dist/esm/components/Chains/VirtualizedChainList.js +2 -1
  32. package/dist/esm/components/Chains/VirtualizedChainList.js.map +1 -1
  33. package/dist/esm/components/Header/HeaderNavigationTabs.js +14 -1
  34. package/dist/esm/components/Header/HeaderNavigationTabs.js.map +1 -1
  35. package/dist/esm/components/IconCircle/IconCircle.style.js +1 -1
  36. package/dist/esm/components/LimitOrderSettings/LimitOrderSettings.js +99 -0
  37. package/dist/esm/components/LimitOrderSettings/LimitOrderSettings.js.map +1 -0
  38. package/dist/esm/components/LimitOrderSettings/LimitOrderSettings.style.js +52 -0
  39. package/dist/esm/components/LimitOrderSettings/LimitOrderSettings.style.js.map +1 -0
  40. package/dist/esm/components/LimitPriceCard/LimitPriceCard.js +115 -0
  41. package/dist/esm/components/LimitPriceCard/LimitPriceCard.js.map +1 -0
  42. package/dist/esm/components/LimitPriceCard/LimitPriceCard.style.js +89 -0
  43. package/dist/esm/components/LimitPriceCard/LimitPriceCard.style.js.map +1 -0
  44. package/dist/esm/components/LimitPriceCard/LimitPricePresets.js +91 -0
  45. package/dist/esm/components/LimitPriceCard/LimitPricePresets.js.map +1 -0
  46. package/dist/esm/components/RouteCard/RouteProviderCard.js +118 -0
  47. package/dist/esm/components/RouteCard/RouteProviderCard.js.map +1 -0
  48. package/dist/esm/components/Routes/LimitOrderRoutes.js +34 -0
  49. package/dist/esm/components/Routes/LimitOrderRoutes.js.map +1 -0
  50. package/dist/esm/components/Routes/Routes.js +2 -2
  51. package/dist/esm/components/Routes/Routes.js.map +1 -1
  52. package/dist/esm/components/SettingsPanel/SettingsPanel.js +51 -0
  53. package/dist/esm/components/SettingsPanel/SettingsPanel.js.map +1 -0
  54. package/dist/esm/components/SettingsPanel/SettingsPanel.style.js +33 -0
  55. package/dist/esm/components/SettingsPanel/SettingsPanel.style.js.map +1 -0
  56. package/dist/esm/components/SettingsPanel/utils.js +14 -0
  57. package/dist/esm/components/SettingsPanel/utils.js.map +1 -0
  58. package/dist/esm/components/SwapButton/SwapButton.js +50 -0
  59. package/dist/esm/components/SwapButton/SwapButton.js.map +1 -0
  60. package/dist/esm/components/SwapButton/SwapButton.style.js +24 -0
  61. package/dist/esm/components/SwapButton/SwapButton.style.js.map +1 -0
  62. package/dist/esm/components/TokenPillButton/TokenPillButton.js +52 -0
  63. package/dist/esm/components/TokenPillButton/TokenPillButton.js.map +1 -0
  64. package/dist/esm/components/TokenPillButton/TokenPillButton.style.js +39 -0
  65. package/dist/esm/components/TokenPillButton/TokenPillButton.style.js.map +1 -0
  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/useChainSearch.js +2 -1
  70. package/dist/esm/hooks/useChainSearch.js.map +1 -1
  71. package/dist/esm/hooks/useLimitMarketRate.js +24 -0
  72. package/dist/esm/hooks/useLimitMarketRate.js.map +1 -0
  73. package/dist/esm/hooks/useLinkedLimitFields.js +49 -0
  74. package/dist/esm/hooks/useLinkedLimitFields.js.map +1 -0
  75. package/dist/esm/hooks/useRoutes.js +23 -6
  76. package/dist/esm/hooks/useRoutes.js.map +1 -1
  77. package/dist/esm/hooks/useTokens.js +16 -14
  78. package/dist/esm/hooks/useTokens.js.map +1 -1
  79. package/dist/esm/hooks/useWideVariant.js +5 -2
  80. package/dist/esm/hooks/useWideVariant.js.map +1 -1
  81. package/dist/esm/i18n/en.json +22 -0
  82. package/dist/esm/index.d.ts +2 -2
  83. package/dist/esm/pages/MainPage/LimitPageContent.js +23 -0
  84. package/dist/esm/pages/MainPage/LimitPageContent.js.map +1 -0
  85. package/dist/esm/pages/MainPage/MainPage.js +19 -22
  86. package/dist/esm/pages/MainPage/MainPage.js.map +1 -1
  87. package/dist/esm/pages/MainPage/MainPageActions.js +28 -0
  88. package/dist/esm/pages/MainPage/MainPageActions.js.map +1 -0
  89. package/dist/esm/pages/MainPage/ReviewButton.js +9 -2
  90. package/dist/esm/pages/MainPage/ReviewButton.js.map +1 -1
  91. package/dist/esm/pages/RoutePriorityPage.js +48 -0
  92. package/dist/esm/pages/RoutePriorityPage.js.map +1 -0
  93. package/dist/esm/pages/SettingsPage/BridgeAndExchangeSettings.js +2 -2
  94. package/dist/esm/pages/SettingsPage/BridgeAndExchangeSettings.js.map +1 -1
  95. package/dist/esm/pages/SettingsPage/RoutePrioritySettings.js +12 -27
  96. package/dist/esm/pages/SettingsPage/RoutePrioritySettings.js.map +1 -1
  97. package/dist/esm/pages/SettingsPage/SlippageSettings/SlippagePage.js +133 -0
  98. package/dist/esm/pages/SettingsPage/SlippageSettings/SlippagePage.js.map +1 -0
  99. package/dist/esm/pages/SettingsPage/SlippageSettings/SlippageSettings.js +14 -72
  100. package/dist/esm/pages/SettingsPage/SlippageSettings/SlippageSettings.js.map +1 -1
  101. package/dist/esm/pages/SettingsPage/SlippageSettings/SlippageSettings.style.js +26 -14
  102. package/dist/esm/pages/SettingsPage/SlippageSettings/SlippageSettings.style.js.map +1 -1
  103. package/dist/esm/pages/SettingsPage/ThemeSettings.js +4 -0
  104. package/dist/esm/pages/SettingsPage/ThemeSettings.js.map +1 -1
  105. package/dist/esm/pages/TransactionPage/TransactionReview.js +4 -1
  106. package/dist/esm/pages/TransactionPage/TransactionReview.js.map +1 -1
  107. package/dist/esm/providers/I18nProvider/I18nProvider.js +1 -1
  108. package/dist/esm/providers/I18nProvider/enResource.d.ts +1 -1
  109. package/dist/esm/providers/I18nProvider/enResource.js +1 -1
  110. package/dist/esm/providers/I18nProvider/types.d.ts +2 -2
  111. package/dist/esm/providers/SDKClientProvider.js +25 -24
  112. package/dist/esm/providers/SDKClientProvider.js.map +1 -1
  113. package/dist/esm/providers/WidgetProvider/WidgetProvider.js +22 -21
  114. package/dist/esm/providers/WidgetProvider/WidgetProvider.js.map +1 -1
  115. package/dist/esm/stores/form/URLSearchParamsBuilder.js +14 -3
  116. package/dist/esm/stores/form/URLSearchParamsBuilder.js.map +1 -1
  117. package/dist/esm/stores/form/createFormStore.js +6 -2
  118. package/dist/esm/stores/form/createFormStore.js.map +1 -1
  119. package/dist/esm/stores/form/getDefaultValuesFromQueryString.js +3 -0
  120. package/dist/esm/stores/form/getDefaultValuesFromQueryString.js.map +1 -1
  121. package/dist/esm/stores/form/types.d.ts +9 -1
  122. package/dist/esm/stores/form/types.d.ts.map +1 -1
  123. package/dist/esm/stores/form/types.js.map +1 -1
  124. package/dist/esm/stores/form/useClearAmountFields.js +1 -0
  125. package/dist/esm/stores/form/useClearAmountFields.js.map +1 -1
  126. package/dist/esm/stores/navigationTabs/useNavigationTabsStore.js +4 -2
  127. package/dist/esm/stores/navigationTabs/useNavigationTabsStore.js.map +1 -1
  128. package/dist/esm/stores/navigationTabs/utils.js +38 -8
  129. package/dist/esm/stores/navigationTabs/utils.js.map +1 -1
  130. package/dist/esm/types/events.d.ts +10 -1
  131. package/dist/esm/types/events.d.ts.map +1 -1
  132. package/dist/esm/types/events.js +2 -0
  133. package/dist/esm/types/events.js.map +1 -1
  134. package/dist/esm/types/widget.d.ts +9 -0
  135. package/dist/esm/types/widget.d.ts.map +1 -1
  136. package/dist/esm/types/widget.js.map +1 -1
  137. package/dist/esm/utils/format.d.ts +1 -1
  138. package/dist/esm/utils/format.d.ts.map +1 -1
  139. package/dist/esm/utils/format.js +2 -2
  140. package/dist/esm/utils/format.js.map +1 -1
  141. package/dist/esm/utils/limitOrder.js +61 -0
  142. package/dist/esm/utils/limitOrder.js.map +1 -0
  143. package/dist/esm/utils/navigationRoutes.d.ts +2 -0
  144. package/dist/esm/utils/navigationRoutes.d.ts.map +1 -1
  145. package/dist/esm/utils/navigationRoutes.js +6 -0
  146. package/dist/esm/utils/navigationRoutes.js.map +1 -1
  147. package/package.json +11 -11
  148. package/src/AppDefault.tsx +16 -0
  149. package/src/components/AmountInput/AmountInputEndAdornment.style.ts +1 -1
  150. package/src/components/AmountInput/AmountInputEndAdornment.tsx +5 -2
  151. package/src/components/AmountInputCard/AmountInputCard.style.tsx +157 -0
  152. package/src/components/AmountInputCard/AmountInputCardPair.tsx +25 -0
  153. package/src/components/AmountInputCard/BalanceDisplay.style.tsx +9 -0
  154. package/src/components/AmountInputCard/BalanceDisplay.tsx +46 -0
  155. package/src/components/AmountInputCard/FiatValueToggle.tsx +55 -0
  156. package/src/components/AmountInputCard/PercentageChips.style.tsx +60 -0
  157. package/src/components/AmountInputCard/PercentageChips.tsx +100 -0
  158. package/src/components/AmountInputCard/ReceiveAmountCard.tsx +219 -0
  159. package/src/components/AmountInputCard/SendAmountCard.tsx +179 -0
  160. package/src/components/Avatar/Avatar.tsx +8 -3
  161. package/src/components/Header/HeaderNavigationTabs.tsx +19 -2
  162. package/src/components/LimitOrderSettings/LimitOrderSettings.style.tsx +69 -0
  163. package/src/components/LimitOrderSettings/LimitOrderSettings.tsx +124 -0
  164. package/src/components/LimitPriceCard/LimitPriceCard.style.tsx +103 -0
  165. package/src/components/LimitPriceCard/LimitPriceCard.tsx +153 -0
  166. package/src/components/LimitPriceCard/LimitPricePresets.tsx +139 -0
  167. package/src/components/RouteCard/RouteProviderCard.tsx +113 -0
  168. package/src/components/Routes/LimitOrderRoutes.tsx +64 -0
  169. package/src/components/Routes/Routes.tsx +3 -2
  170. package/src/components/SettingsPanel/SettingsPanel.style.tsx +41 -0
  171. package/src/components/SettingsPanel/SettingsPanel.tsx +73 -0
  172. package/src/components/SettingsPanel/utils.ts +27 -0
  173. package/src/components/SwapButton/SwapButton.style.tsx +28 -0
  174. package/src/components/SwapButton/SwapButton.tsx +71 -0
  175. package/src/components/TokenPillButton/TokenPillButton.style.tsx +50 -0
  176. package/src/components/TokenPillButton/TokenPillButton.tsx +81 -0
  177. package/src/config/version.ts +1 -1
  178. package/src/hooks/useLimitMarketRate.ts +39 -0
  179. package/src/hooks/useLinkedLimitFields.ts +74 -0
  180. package/src/hooks/useRoutes.ts +45 -4
  181. package/src/hooks/useWideVariant.ts +8 -2
  182. package/src/pages/MainPage/LimitPageContent.tsx +18 -0
  183. package/src/pages/MainPage/MainPage.tsx +43 -30
  184. package/src/pages/MainPage/MainPageActions.tsx +35 -0
  185. package/src/pages/MainPage/ReviewButton.tsx +13 -1
  186. package/src/pages/RoutePriorityPage.tsx +55 -0
  187. package/src/pages/SettingsPage/RoutePrioritySettings.tsx +14 -38
  188. package/src/pages/SettingsPage/SlippageSettings/SlippagePage.tsx +176 -0
  189. package/src/pages/SettingsPage/SlippageSettings/SlippageSettings.style.tsx +32 -25
  190. package/src/pages/SettingsPage/SlippageSettings/SlippageSettings.tsx +14 -128
  191. package/src/pages/SettingsPage/ThemeSettings.tsx +4 -0
  192. package/src/stores/form/URLSearchParamsBuilder.tsx +25 -3
  193. package/src/stores/form/createFormStore.ts +7 -3
  194. package/src/stores/form/getDefaultValuesFromQueryString.ts +11 -0
  195. package/src/stores/form/types.ts +14 -1
  196. package/src/stores/form/useClearAmountFields.ts +1 -0
  197. package/src/stores/navigationTabs/types.ts +6 -0
  198. package/src/stores/navigationTabs/useNavigationTabsStore.tsx +8 -3
  199. package/src/stores/navigationTabs/utils.ts +67 -5
  200. package/src/types/events.ts +12 -0
  201. package/src/types/widget.ts +9 -0
  202. package/src/utils/format.ts +6 -2
  203. package/src/utils/limitOrder.ts +88 -0
  204. package/src/utils/navigationRoutes.ts +6 -0
@@ -18,5 +18,5 @@ export const AmountInputButton: React.FC<
18
18
  fontWeight: 700,
19
19
  lineHeight: 1.3334,
20
20
  height: 'auto',
21
- borderRadius: theme.shape.borderRadiusSecondary,
21
+ borderRadius: theme.vars.shape.borderRadiusSecondary,
22
22
  }))
@@ -1,4 +1,5 @@
1
1
  import { formatUnits } from '@lifi/sdk'
2
+ import { useAccount } from '@lifi/wallet-management'
2
3
  import type React from 'react'
3
4
  import { type JSX, memo } from 'react'
4
5
  import { useTranslation } from 'react-i18next'
@@ -25,6 +26,9 @@ export const AmountInputEndAdornment: React.NamedExoticComponent<FormTypeProps>
25
26
  FormKeyHelper.getTokenKey(formType)
26
27
  )
27
28
 
29
+ const chain = getChainById(chainId)
30
+ const { account } = useAccount({ chainType: chain?.chainType })
31
+
28
32
  // We get gas recommendations for the source chain to make sure that after pressing the Max button
29
33
  // the user will have enough funds remaining to cover gas costs
30
34
  const { data } = useGasRecommendation(chainId)
@@ -35,7 +39,6 @@ export const AmountInputEndAdornment: React.NamedExoticComponent<FormTypeProps>
35
39
  if (!token?.amount) {
36
40
  return 0n
37
41
  }
38
- const chain = getChainById(chainId)
39
42
  let maxAmount = token.amount
40
43
  if (chain?.nativeToken.address === tokenAddress && data?.recommended) {
41
44
  const recommendedAmount = BigInt(data.recommended.amount)
@@ -73,7 +76,7 @@ export const AmountInputEndAdornment: React.NamedExoticComponent<FormTypeProps>
73
76
  }
74
77
  }
75
78
 
76
- if (formType !== 'from' || !token) {
79
+ if (formType !== 'from' || !token || !account?.isConnected) {
77
80
  return null
78
81
  }
79
82
 
@@ -0,0 +1,157 @@
1
+ import {
2
+ Box,
3
+ ButtonBase,
4
+ InputBase,
5
+ inputBaseClasses,
6
+ styled,
7
+ Typography,
8
+ } from '@mui/material'
9
+ import type React from 'react'
10
+ import type { FormType } from '../../stores/form/types.js'
11
+ import { InputCard } from '../Card/InputCard.js'
12
+
13
+ export const maxInputFontSize = 30
14
+ export const minInputFontSize = 20
15
+ export const amountHeight = 32
16
+ export const footerFontSize = 12
17
+
18
+ export const AmountCard: React.FC<
19
+ React.ComponentProps<typeof InputCard> & {
20
+ formType?: FormType
21
+ mask?: boolean
22
+ }
23
+ > = styled(InputCard, {
24
+ shouldForwardProp: (prop) => !['formType', 'mask'].includes(prop as string),
25
+ })<{ formType?: FormType; mask?: boolean }>(
26
+ ({ theme, formType, mask = true }) => {
27
+ const cardVariant = theme.components?.MuiCard?.defaultProps?.variant
28
+ // Notch faces the swap button between the stacked cards: bottom edge for the
29
+ // top (send) card, top edge for the bottom (receive) card.
30
+ const vertical = formType === 'to' ? '-4px' : 'calc(100% + 4px)'
31
+ return {
32
+ display: 'flex',
33
+ flexDirection: 'column',
34
+ gap: theme.spacing(1.5),
35
+ padding: theme.spacing(2),
36
+ ...(cardVariant !== 'outlined' &&
37
+ mask && {
38
+ mask: `radial-gradient(circle 22px at 50% ${vertical}, #fff0 96%, #fff) 100% 100% / 100% 100% no-repeat`,
39
+ }),
40
+ }
41
+ }
42
+ )
43
+
44
+ export const CardHeaderRow: React.FC<React.ComponentProps<typeof Box>> = styled(
45
+ Box
46
+ )(({ theme }) => ({
47
+ display: 'flex',
48
+ justifyContent: 'space-between',
49
+ alignItems: 'center',
50
+ padding: 0,
51
+ height: theme.spacing(2),
52
+ }))
53
+
54
+ export const CardBodyRow: React.FC<React.ComponentProps<typeof Box>> = styled(
55
+ Box
56
+ )(({ theme }) => ({
57
+ display: 'flex',
58
+ alignItems: 'center',
59
+ gap: theme.spacing(2),
60
+ }))
61
+
62
+ export const CardFooterRow: React.FC<React.ComponentProps<typeof Box>> = styled(
63
+ Box
64
+ )(({ theme }) => ({
65
+ display: 'flex',
66
+ justifyContent: 'space-between',
67
+ alignItems: 'center',
68
+ height: theme.spacing(2),
69
+ }))
70
+
71
+ export const LargeInput: React.FC<React.ComponentProps<typeof InputBase>> =
72
+ styled(InputBase)(({ theme }) => ({
73
+ fontSize: maxInputFontSize,
74
+ fontWeight: 700,
75
+ lineHeight: 1.4,
76
+ height: amountHeight,
77
+ flex: 1,
78
+ minWidth: 0,
79
+ boxShadow: 'none',
80
+ [`.${inputBaseClasses.input}`]: {
81
+ height: amountHeight,
82
+ padding: 0,
83
+ },
84
+ [`.${inputBaseClasses.input}::placeholder`]: {
85
+ color: theme.vars.palette.text.primary,
86
+ opacity: 0.5,
87
+ },
88
+ '& input[type="number"]::-webkit-outer-spin-button, & input[type="number"]::-webkit-inner-spin-button':
89
+ {
90
+ WebkitAppearance: 'none',
91
+ margin: 0,
92
+ },
93
+ '& input[type="number"]': {
94
+ MozAppearance: 'textfield',
95
+ },
96
+ [`&.${inputBaseClasses.disabled}`]: {
97
+ color: 'inherit',
98
+ },
99
+ [`.${inputBaseClasses.input}.${inputBaseClasses.disabled}`]: {
100
+ WebkitTextFillColor: 'unset',
101
+ },
102
+ }))
103
+
104
+ export const AmountDisplay: React.FC<React.ComponentProps<typeof Typography>> =
105
+ styled(Typography)(() => ({
106
+ fontSize: maxInputFontSize,
107
+ fontWeight: 700,
108
+ lineHeight: 1.4,
109
+ height: amountHeight,
110
+ display: 'flex',
111
+ alignItems: 'center',
112
+ flex: 1,
113
+ minWidth: 0,
114
+ overflow: 'visible',
115
+ whiteSpace: 'nowrap',
116
+ }))
117
+
118
+ export const FooterText: React.FC<React.ComponentProps<typeof Typography>> =
119
+ styled(Typography)(({ theme }) => ({
120
+ fontSize: footerFontSize,
121
+ fontWeight: 500,
122
+ lineHeight: 1,
123
+ color: theme.vars.palette.text.secondary,
124
+ }))
125
+
126
+ export const CardContainer: React.FC<React.ComponentProps<typeof Box>> = styled(
127
+ Box
128
+ )(() => ({
129
+ display: 'flex',
130
+ flexDirection: 'column',
131
+ position: 'relative',
132
+ }))
133
+
134
+ export const ToggleButton: React.FC<
135
+ React.ComponentProps<typeof ButtonBase> & { clickable?: boolean }
136
+ > = styled(ButtonBase, {
137
+ shouldForwardProp: (prop) => prop !== 'clickable',
138
+ })<{ clickable?: boolean }>(({ theme, clickable }) => ({
139
+ display: 'flex',
140
+ alignItems: 'center',
141
+ gap: theme.spacing(0.25),
142
+ borderRadius: theme.vars.shape.borderRadiusSecondary,
143
+ padding: theme.spacing(0.25, 0.5, 0.25, 0.75),
144
+ marginLeft: theme.spacing(-0.75),
145
+ backgroundColor: 'transparent',
146
+ ...(clickable
147
+ ? {
148
+ cursor: 'pointer',
149
+ '&:hover': {
150
+ backgroundColor: `color-mix(in srgb, ${theme.vars.palette.common.onBackground} 4%, transparent)`,
151
+ },
152
+ }
153
+ : {
154
+ cursor: 'default',
155
+ pointerEvents: 'none',
156
+ }),
157
+ }))
@@ -0,0 +1,25 @@
1
+ import type { BoxProps } from '@mui/material'
2
+ import type React from 'react'
3
+ import type { JSX } from 'react'
4
+ import { useWidgetConfig } from '../../providers/WidgetProvider/WidgetProvider.js'
5
+ import { LimitPriceCard } from '../LimitPriceCard/LimitPriceCard.js'
6
+ import { SwapButton } from '../SwapButton/SwapButton.js'
7
+ import { CardContainer } from './AmountInputCard.style.js'
8
+ import { ReceiveAmountCard } from './ReceiveAmountCard.js'
9
+ import { SendAmountCard } from './SendAmountCard.js'
10
+
11
+ export const AmountInputCardPair: React.FC<BoxProps> = (props): JSX.Element => {
12
+ // Refuel's destination is fixed to native gas, so reversing from/to is
13
+ // meaningless — hide the swap button (and the notch mask it sits in).
14
+ const { mode } = useWidgetConfig()
15
+ const showSwapButton = mode !== 'refuel'
16
+
17
+ return (
18
+ <CardContainer {...props}>
19
+ {mode === 'limit' ? <LimitPriceCard sx={{ marginBottom: 1 }} /> : null}
20
+ <SendAmountCard mask={showSwapButton} />
21
+ <SwapButton sx={{ visibility: showSwapButton ? 'visible' : 'hidden' }} />
22
+ <ReceiveAmountCard mask={showSwapButton} />
23
+ </CardContainer>
24
+ )
25
+ }
@@ -0,0 +1,9 @@
1
+ import { Box, styled } from '@mui/material'
2
+ import type React from 'react'
3
+
4
+ export const BalanceContainer: React.FC<React.ComponentProps<typeof Box>> =
5
+ styled(Box)(({ theme }) => ({
6
+ display: 'flex',
7
+ alignItems: 'center',
8
+ gap: theme.spacing(0.5),
9
+ }))
@@ -0,0 +1,46 @@
1
+ import AccountBalanceWalletOutlinedIcon from '@mui/icons-material/AccountBalanceWalletOutlined'
2
+ import { Skeleton } from '@mui/material'
3
+ import { type JSX, memo } from 'react'
4
+ import { useTranslation } from 'react-i18next'
5
+ import { useTokenAddressBalance } from '../../hooks/useTokenAddressBalance.js'
6
+ import type { FormTypeProps } from '../../stores/form/types.js'
7
+ import { FormKeyHelper } from '../../stores/form/types.js'
8
+ import { useFieldValues } from '../../stores/form/useFieldValues.js'
9
+ import { formatTokenAmount } from '../../utils/format.js'
10
+ import { FooterText, footerFontSize } from './AmountInputCard.style.js'
11
+ import { BalanceContainer } from './BalanceDisplay.style.js'
12
+
13
+ export const BalanceDisplay: React.NamedExoticComponent<FormTypeProps> = memo(
14
+ ({ formType }: FormTypeProps): JSX.Element | null => {
15
+ const { t } = useTranslation()
16
+ const [chainId, tokenAddress] = useFieldValues(
17
+ FormKeyHelper.getChainKey(formType),
18
+ FormKeyHelper.getTokenKey(formType)
19
+ )
20
+
21
+ const { token, isLoading } = useTokenAddressBalance(chainId, tokenAddress)
22
+
23
+ if (isLoading && tokenAddress) {
24
+ return (
25
+ <Skeleton variant="text" width={72} sx={{ fontSize: footerFontSize }} />
26
+ )
27
+ }
28
+
29
+ if (!token?.amount) {
30
+ return null
31
+ }
32
+
33
+ const tokenAmount = formatTokenAmount(token.amount, token.decimals)
34
+
35
+ return (
36
+ <BalanceContainer>
37
+ <FooterText title={tokenAmount}>
38
+ {t('format.tokenAmount', { value: tokenAmount })}
39
+ </FooterText>
40
+ <AccountBalanceWalletOutlinedIcon
41
+ sx={{ fontSize: 16, color: 'text.secondary' }}
42
+ />
43
+ </BalanceContainer>
44
+ )
45
+ }
46
+ )
@@ -0,0 +1,55 @@
1
+ import SwapVertIcon from '@mui/icons-material/SwapVert'
2
+ import { type JSX, memo } from 'react'
3
+ import { useTranslation } from 'react-i18next'
4
+ import { useToken } from '../../hooks/useToken.js'
5
+ import type { FormTypeProps } from '../../stores/form/types.js'
6
+ import { FormKeyHelper } from '../../stores/form/types.js'
7
+ import { useFieldValues } from '../../stores/form/useFieldValues.js'
8
+ import { useInputModeStore } from '../../stores/inputMode/useInputModeStore.js'
9
+ import { formatTokenPrice } from '../../utils/format.js'
10
+ import { FooterText, ToggleButton } from './AmountInputCard.style.js'
11
+
12
+ export const FiatValueToggle: React.NamedExoticComponent<FormTypeProps> = memo(
13
+ ({ formType }: FormTypeProps): JSX.Element => {
14
+ const { t } = useTranslation()
15
+ const [chainId, tokenAddress, amount] = useFieldValues(
16
+ FormKeyHelper.getChainKey(formType),
17
+ FormKeyHelper.getTokenKey(formType),
18
+ FormKeyHelper.getAmountKey(formType)
19
+ )
20
+
21
+ const { token } = useToken(chainId, tokenAddress)
22
+ const { inputMode, toggleInputMode } = useInputModeStore()
23
+ const currentInputMode = inputMode[formType]
24
+
25
+ const handleToggle = (e: React.MouseEvent): void => {
26
+ e.stopPropagation()
27
+ toggleInputMode(formType)
28
+ }
29
+
30
+ const canToggle = !!token?.priceUSD
31
+
32
+ const displayValue =
33
+ currentInputMode === 'amount'
34
+ ? t('format.currency', {
35
+ value: formatTokenPrice(amount, token?.priceUSD, token?.decimals),
36
+ })
37
+ : t('format.tokenAmount', { value: amount || '0' })
38
+
39
+ return (
40
+ <ToggleButton
41
+ clickable={canToggle}
42
+ onClick={canToggle ? handleToggle : undefined}
43
+ aria-label={canToggle ? 'Toggle fiat value' : undefined}
44
+ >
45
+ <FooterText>{displayValue}</FooterText>
46
+ {currentInputMode === 'price' && token?.symbol ? (
47
+ <FooterText>{token.symbol}</FooterText>
48
+ ) : null}
49
+ {canToggle ? (
50
+ <SwapVertIcon sx={{ fontSize: 14, color: 'text.secondary' }} />
51
+ ) : null}
52
+ </ToggleButton>
53
+ )
54
+ }
55
+ )
@@ -0,0 +1,60 @@
1
+ import { Box, Button, styled } from '@mui/material'
2
+ import { cardClasses } from '@mui/material/Card'
3
+ import type React from 'react'
4
+
5
+ const chipCount = 4
6
+ const staggerStepMs = 25
7
+
8
+ const revealStagger = Object.fromEntries(
9
+ Array.from({ length: chipCount }, (_, index) => [
10
+ `&[data-delay="${index}"]`,
11
+ {
12
+ [`.${cardClasses.root}:hover &`]: {
13
+ opacity: 1,
14
+ transform: 'scale(1) translateX(0)',
15
+ pointerEvents: 'auto',
16
+ transitionDelay: `${(chipCount - 1 - index) * staggerStepMs}ms`,
17
+ },
18
+ [`.${cardClasses.root}:not(:hover) &`]: {
19
+ transitionDelay: `${index * staggerStepMs}ms`,
20
+ },
21
+ },
22
+ ])
23
+ )
24
+
25
+ export const ChipContainer: React.FC<React.ComponentProps<typeof Box>> = styled(
26
+ Box
27
+ )(({ theme }) => ({
28
+ display: 'flex',
29
+ gap: theme.spacing(0.5),
30
+ }))
31
+
32
+ export const Chip: React.FC<React.ComponentProps<typeof Button>> = styled(
33
+ Button
34
+ )(({ theme }) => ({
35
+ minWidth: 0,
36
+ padding: theme.spacing(0.5, 1.5),
37
+ fontSize: 12,
38
+ fontWeight: 700,
39
+ lineHeight: 1.3334,
40
+ backgroundColor: `color-mix(in srgb, ${theme.vars.palette.common.onBackground} 4%, transparent)`,
41
+ color: theme.vars.palette.text.primary,
42
+ transition:
43
+ 'opacity 200ms cubic-bezier(0.4, 0, 0.2, 1), transform 200ms cubic-bezier(0.4, 0, 0.2, 1), background-color 200ms ease',
44
+ '&:hover': {
45
+ backgroundColor: `color-mix(in srgb, ${theme.vars.palette.common.onBackground} 8%, transparent)`,
46
+ },
47
+ // Reveal-on-hover — only on devices that support hover. On touch there is no
48
+ // hover, so the chips stay permanently visible (the default above) instead of
49
+ // becoming an unreachable dead row.
50
+ '@media (hover: hover)': {
51
+ opacity: 0,
52
+ transform: 'scale(0.85) translateX(10px)',
53
+ pointerEvents: 'none',
54
+ ...revealStagger,
55
+ '@media (prefers-reduced-motion: reduce)': {
56
+ transform: 'none !important',
57
+ transitionDelay: '0ms !important',
58
+ },
59
+ },
60
+ }))
@@ -0,0 +1,100 @@
1
+ import { formatUnits } from '@lifi/sdk'
2
+ import { useAccount } from '@lifi/wallet-management'
3
+ import { type JSX, memo } from 'react'
4
+ import { useTranslation } from 'react-i18next'
5
+ import { useAvailableChains } from '../../hooks/useAvailableChains.js'
6
+ import { useGasRecommendation } from '../../hooks/useGasRecommendation.js'
7
+ import { useLinkedLimitFields } from '../../hooks/useLinkedLimitFields.js'
8
+ import { useTokenAddressBalance } from '../../hooks/useTokenAddressBalance.js'
9
+ import { useWidgetConfig } from '../../providers/WidgetProvider/WidgetProvider.js'
10
+ import type { FormTypeProps } from '../../stores/form/types.js'
11
+ import { FormKeyHelper } from '../../stores/form/types.js'
12
+ import { useFieldActions } from '../../stores/form/useFieldActions.js'
13
+ import { useFieldValues } from '../../stores/form/useFieldValues.js'
14
+ import type { DisabledUIConfig } from '../../types/widget.js'
15
+ import { Chip, ChipContainer } from './PercentageChips.style.js'
16
+
17
+ export const PercentageChips: React.NamedExoticComponent<FormTypeProps> = memo(
18
+ ({ formType }: FormTypeProps): JSX.Element | null => {
19
+ const { t } = useTranslation()
20
+ const { getChainById } = useAvailableChains()
21
+ const { setFieldValue } = useFieldActions()
22
+ const { disabledUI, mode } = useWidgetConfig()
23
+ const { setSendAmount } = useLinkedLimitFields()
24
+
25
+ const amountKey = FormKeyHelper.getAmountKey(formType)
26
+ const isDisabled = !!disabledUI?.[amountKey as keyof DisabledUIConfig]
27
+
28
+ const [chainId, tokenAddress] = useFieldValues(
29
+ FormKeyHelper.getChainKey(formType),
30
+ FormKeyHelper.getTokenKey(formType)
31
+ )
32
+
33
+ const chain = getChainById(chainId)
34
+ const { account } = useAccount({ chainType: chain?.chainType })
35
+
36
+ const { data } = useGasRecommendation(chainId)
37
+ const { token } = useTokenAddressBalance(chainId, tokenAddress)
38
+
39
+ const getMaxAmount = (): bigint => {
40
+ if (!token?.amount) {
41
+ return 0n
42
+ }
43
+ let maxAmount = token.amount
44
+ if (chain?.nativeToken.address === tokenAddress && data?.recommended) {
45
+ const recommendedAmount = BigInt(data.recommended.amount)
46
+ if (token.amount > recommendedAmount) {
47
+ maxAmount = token.amount - recommendedAmount
48
+ }
49
+ }
50
+ return maxAmount
51
+ }
52
+
53
+ // In limit mode the send amount must flow through the linked-field
54
+ // derivation so the receive amount recomputes; otherwise it is a plain
55
+ // form-field write.
56
+ const applyAmount = (value: string): void => {
57
+ if (mode === 'limit') {
58
+ setSendAmount(value)
59
+ } else {
60
+ setFieldValue(amountKey, value, { isTouched: true })
61
+ }
62
+ }
63
+
64
+ const handlePercentage = (percentage: number): void => {
65
+ const maxAmount = getMaxAmount()
66
+ if (maxAmount && token?.decimals) {
67
+ const percentageAmount = (maxAmount * BigInt(percentage)) / 100n
68
+ applyAmount(formatUnits(percentageAmount, token.decimals))
69
+ }
70
+ }
71
+
72
+ const handleMax = (): void => {
73
+ const maxAmount = getMaxAmount()
74
+ if (maxAmount && token?.decimals) {
75
+ applyAmount(formatUnits(maxAmount, token.decimals))
76
+ }
77
+ }
78
+
79
+ if (formType !== 'from' || isDisabled || !token || !account?.isConnected) {
80
+ return null
81
+ }
82
+
83
+ return (
84
+ <ChipContainer>
85
+ <Chip data-delay="0" onClick={() => handlePercentage(25)}>
86
+ 25%
87
+ </Chip>
88
+ <Chip data-delay="1" onClick={() => handlePercentage(50)}>
89
+ 50%
90
+ </Chip>
91
+ <Chip data-delay="2" onClick={() => handlePercentage(75)}>
92
+ 75%
93
+ </Chip>
94
+ <Chip data-delay="3" onClick={handleMax}>
95
+ {t('button.max')}
96
+ </Chip>
97
+ </ChipContainer>
98
+ )
99
+ }
100
+ )