@lifi/widget 4.1.0 → 4.3.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 (210) 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/useFilteredByTokenBalances.js +4 -1
  72. package/dist/esm/hooks/useFilteredByTokenBalances.js.map +1 -1
  73. package/dist/esm/hooks/useLimitMarketRate.js +24 -0
  74. package/dist/esm/hooks/useLimitMarketRate.js.map +1 -0
  75. package/dist/esm/hooks/useLinkedLimitFields.js +49 -0
  76. package/dist/esm/hooks/useLinkedLimitFields.js.map +1 -0
  77. package/dist/esm/hooks/useRoutes.js +23 -6
  78. package/dist/esm/hooks/useRoutes.js.map +1 -1
  79. package/dist/esm/hooks/useTokens.js +16 -14
  80. package/dist/esm/hooks/useTokens.js.map +1 -1
  81. package/dist/esm/hooks/useWideVariant.js +5 -2
  82. package/dist/esm/hooks/useWideVariant.js.map +1 -1
  83. package/dist/esm/i18n/en.json +22 -0
  84. package/dist/esm/index.d.ts +2 -2
  85. package/dist/esm/pages/MainPage/LimitPageContent.js +23 -0
  86. package/dist/esm/pages/MainPage/LimitPageContent.js.map +1 -0
  87. package/dist/esm/pages/MainPage/MainPage.js +19 -22
  88. package/dist/esm/pages/MainPage/MainPage.js.map +1 -1
  89. package/dist/esm/pages/MainPage/MainPageActions.js +28 -0
  90. package/dist/esm/pages/MainPage/MainPageActions.js.map +1 -0
  91. package/dist/esm/pages/MainPage/ReviewButton.js +9 -2
  92. package/dist/esm/pages/MainPage/ReviewButton.js.map +1 -1
  93. package/dist/esm/pages/RoutePriorityPage.js +48 -0
  94. package/dist/esm/pages/RoutePriorityPage.js.map +1 -0
  95. package/dist/esm/pages/SettingsPage/BridgeAndExchangeSettings.js +2 -2
  96. package/dist/esm/pages/SettingsPage/BridgeAndExchangeSettings.js.map +1 -1
  97. package/dist/esm/pages/SettingsPage/RoutePrioritySettings.js +12 -27
  98. package/dist/esm/pages/SettingsPage/RoutePrioritySettings.js.map +1 -1
  99. package/dist/esm/pages/SettingsPage/SlippageSettings/SlippagePage.js +133 -0
  100. package/dist/esm/pages/SettingsPage/SlippageSettings/SlippagePage.js.map +1 -0
  101. package/dist/esm/pages/SettingsPage/SlippageSettings/SlippageSettings.js +14 -72
  102. package/dist/esm/pages/SettingsPage/SlippageSettings/SlippageSettings.js.map +1 -1
  103. package/dist/esm/pages/SettingsPage/SlippageSettings/SlippageSettings.style.js +26 -14
  104. package/dist/esm/pages/SettingsPage/SlippageSettings/SlippageSettings.style.js.map +1 -1
  105. package/dist/esm/pages/SettingsPage/ThemeSettings.js +4 -0
  106. package/dist/esm/pages/SettingsPage/ThemeSettings.js.map +1 -1
  107. package/dist/esm/pages/TransactionPage/TransactionReview.js +4 -1
  108. package/dist/esm/pages/TransactionPage/TransactionReview.js.map +1 -1
  109. package/dist/esm/providers/I18nProvider/I18nProvider.js +1 -1
  110. package/dist/esm/providers/I18nProvider/enResource.d.ts +1 -1
  111. package/dist/esm/providers/I18nProvider/enResource.js +1 -1
  112. package/dist/esm/providers/I18nProvider/types.d.ts +2 -2
  113. package/dist/esm/providers/SDKClientProvider.js +25 -24
  114. package/dist/esm/providers/SDKClientProvider.js.map +1 -1
  115. package/dist/esm/providers/WidgetProvider/WidgetProvider.js +22 -21
  116. package/dist/esm/providers/WidgetProvider/WidgetProvider.js.map +1 -1
  117. package/dist/esm/stores/form/URLSearchParamsBuilder.js +14 -3
  118. package/dist/esm/stores/form/URLSearchParamsBuilder.js.map +1 -1
  119. package/dist/esm/stores/form/createFormStore.js +6 -2
  120. package/dist/esm/stores/form/createFormStore.js.map +1 -1
  121. package/dist/esm/stores/form/getDefaultValuesFromQueryString.js +3 -0
  122. package/dist/esm/stores/form/getDefaultValuesFromQueryString.js.map +1 -1
  123. package/dist/esm/stores/form/types.d.ts +9 -1
  124. package/dist/esm/stores/form/types.d.ts.map +1 -1
  125. package/dist/esm/stores/form/types.js.map +1 -1
  126. package/dist/esm/stores/form/useClearAmountFields.js +1 -0
  127. package/dist/esm/stores/form/useClearAmountFields.js.map +1 -1
  128. package/dist/esm/stores/navigationTabs/useNavigationTabsStore.js +4 -2
  129. package/dist/esm/stores/navigationTabs/useNavigationTabsStore.js.map +1 -1
  130. package/dist/esm/stores/navigationTabs/utils.js +38 -8
  131. package/dist/esm/stores/navigationTabs/utils.js.map +1 -1
  132. package/dist/esm/types/events.d.ts +10 -1
  133. package/dist/esm/types/events.d.ts.map +1 -1
  134. package/dist/esm/types/events.js +2 -0
  135. package/dist/esm/types/events.js.map +1 -1
  136. package/dist/esm/types/widget.d.ts +10 -0
  137. package/dist/esm/types/widget.d.ts.map +1 -1
  138. package/dist/esm/types/widget.js.map +1 -1
  139. package/dist/esm/utils/format.d.ts +1 -1
  140. package/dist/esm/utils/format.d.ts.map +1 -1
  141. package/dist/esm/utils/format.js +2 -2
  142. package/dist/esm/utils/format.js.map +1 -1
  143. package/dist/esm/utils/limitOrder.js +61 -0
  144. package/dist/esm/utils/limitOrder.js.map +1 -0
  145. package/dist/esm/utils/navigationRoutes.d.ts +2 -0
  146. package/dist/esm/utils/navigationRoutes.d.ts.map +1 -1
  147. package/dist/esm/utils/navigationRoutes.js +6 -0
  148. package/dist/esm/utils/navigationRoutes.js.map +1 -1
  149. package/dist/esm/utils/token.js +38 -3
  150. package/dist/esm/utils/token.js.map +1 -1
  151. package/package.json +11 -11
  152. package/src/AppDefault.tsx +16 -0
  153. package/src/components/AmountInput/AmountInputEndAdornment.style.ts +1 -1
  154. package/src/components/AmountInput/AmountInputEndAdornment.tsx +5 -2
  155. package/src/components/AmountInputCard/AmountInputCard.style.tsx +157 -0
  156. package/src/components/AmountInputCard/AmountInputCardPair.tsx +25 -0
  157. package/src/components/AmountInputCard/BalanceDisplay.style.tsx +9 -0
  158. package/src/components/AmountInputCard/BalanceDisplay.tsx +46 -0
  159. package/src/components/AmountInputCard/FiatValueToggle.tsx +55 -0
  160. package/src/components/AmountInputCard/PercentageChips.style.tsx +60 -0
  161. package/src/components/AmountInputCard/PercentageChips.tsx +100 -0
  162. package/src/components/AmountInputCard/ReceiveAmountCard.tsx +219 -0
  163. package/src/components/AmountInputCard/SendAmountCard.tsx +179 -0
  164. package/src/components/Avatar/Avatar.tsx +8 -3
  165. package/src/components/Header/HeaderNavigationTabs.tsx +19 -2
  166. package/src/components/LimitOrderSettings/LimitOrderSettings.style.tsx +69 -0
  167. package/src/components/LimitOrderSettings/LimitOrderSettings.tsx +124 -0
  168. package/src/components/LimitPriceCard/LimitPriceCard.style.tsx +103 -0
  169. package/src/components/LimitPriceCard/LimitPriceCard.tsx +153 -0
  170. package/src/components/LimitPriceCard/LimitPricePresets.tsx +139 -0
  171. package/src/components/RouteCard/RouteProviderCard.tsx +113 -0
  172. package/src/components/Routes/LimitOrderRoutes.tsx +64 -0
  173. package/src/components/Routes/Routes.tsx +3 -2
  174. package/src/components/SettingsPanel/SettingsPanel.style.tsx +41 -0
  175. package/src/components/SettingsPanel/SettingsPanel.tsx +73 -0
  176. package/src/components/SettingsPanel/utils.ts +27 -0
  177. package/src/components/SwapButton/SwapButton.style.tsx +28 -0
  178. package/src/components/SwapButton/SwapButton.tsx +71 -0
  179. package/src/components/TokenPillButton/TokenPillButton.style.tsx +50 -0
  180. package/src/components/TokenPillButton/TokenPillButton.tsx +81 -0
  181. package/src/config/version.ts +1 -1
  182. package/src/hooks/useFilteredByTokenBalances.ts +7 -2
  183. package/src/hooks/useLimitMarketRate.ts +39 -0
  184. package/src/hooks/useLinkedLimitFields.ts +74 -0
  185. package/src/hooks/useRoutes.ts +45 -4
  186. package/src/hooks/useWideVariant.ts +8 -2
  187. package/src/pages/MainPage/LimitPageContent.tsx +18 -0
  188. package/src/pages/MainPage/MainPage.tsx +43 -30
  189. package/src/pages/MainPage/MainPageActions.tsx +35 -0
  190. package/src/pages/MainPage/ReviewButton.tsx +13 -1
  191. package/src/pages/RoutePriorityPage.tsx +55 -0
  192. package/src/pages/SettingsPage/RoutePrioritySettings.tsx +14 -38
  193. package/src/pages/SettingsPage/SlippageSettings/SlippagePage.tsx +176 -0
  194. package/src/pages/SettingsPage/SlippageSettings/SlippageSettings.style.tsx +32 -25
  195. package/src/pages/SettingsPage/SlippageSettings/SlippageSettings.tsx +14 -128
  196. package/src/pages/SettingsPage/ThemeSettings.tsx +4 -0
  197. package/src/stores/form/URLSearchParamsBuilder.tsx +25 -3
  198. package/src/stores/form/createFormStore.ts +7 -3
  199. package/src/stores/form/getDefaultValuesFromQueryString.ts +11 -0
  200. package/src/stores/form/types.ts +14 -1
  201. package/src/stores/form/useClearAmountFields.ts +1 -0
  202. package/src/stores/navigationTabs/types.ts +6 -0
  203. package/src/stores/navigationTabs/useNavigationTabsStore.tsx +8 -3
  204. package/src/stores/navigationTabs/utils.ts +67 -5
  205. package/src/types/events.ts +12 -0
  206. package/src/types/widget.ts +10 -0
  207. package/src/utils/format.ts +6 -2
  208. package/src/utils/limitOrder.ts +88 -0
  209. package/src/utils/navigationRoutes.ts +6 -0
  210. package/src/utils/token.ts +71 -20
@@ -0,0 +1,176 @@
1
+ import Check from '@mui/icons-material/Check'
2
+ import CloseRounded from '@mui/icons-material/CloseRounded'
3
+ import WarningRounded from '@mui/icons-material/WarningRounded'
4
+ import { Box, Collapse, List, Typography } from '@mui/material'
5
+ import type { ChangeEventHandler, FocusEventHandler } from 'react'
6
+ import { useRef, useState } from 'react'
7
+ import { useTranslation } from 'react-i18next'
8
+ import { ListItemText } from '../../../components/ListItemText.js'
9
+ import { PageContainer } from '../../../components/PageContainer.js'
10
+ import { SettingsListItemButton } from '../../../components/SettingsListItemButton.js'
11
+ import { useHeader } from '../../../hooks/useHeader.js'
12
+ import { useListHeight } from '../../../hooks/useListHeight.js'
13
+ import { useSettingMonitor } from '../../../hooks/useSettingMonitor.js'
14
+ import { defaultSlippage } from '../../../stores/settings/createSettingsStore.js'
15
+ import { useSettings } from '../../../stores/settings/useSettings.js'
16
+ import { useSettingsActions } from '../../../stores/settings/useSettingsActions.js'
17
+ import { formatInputAmount, formatSlippage } from '../../../utils/format.js'
18
+ import {
19
+ SlippageClearButton,
20
+ SlippageInput,
21
+ SlippageLimitsWarningContainer,
22
+ } from './SlippageSettings.style.js'
23
+
24
+ const slippagePresets = ['0.5', '1']
25
+ const maxFractionDigits = 5
26
+
27
+ export const SlippagePage: React.FC = () => {
28
+ const { t } = useTranslation()
29
+ const {
30
+ isSlippageNotRecommended,
31
+ isSlippageUnderRecommendedLimits,
32
+ isSlippageOutsideRecommendedLimits,
33
+ } = useSettingMonitor()
34
+ const { slippage } = useSettings(['slippage'])
35
+ const { setValue } = useSettingsActions()
36
+ const defaultValue = useRef(slippage)
37
+ const listParentRef = useRef<HTMLUListElement | null>(null)
38
+ const { listHeight } = useListHeight({ listParentRef })
39
+
40
+ useHeader(t('settings.slippage'))
41
+
42
+ const isAuto = !slippage || slippage === defaultSlippage
43
+ const isPreset = !!slippage && slippagePresets.includes(slippage)
44
+ const isCustom = !isAuto && !isPreset
45
+
46
+ const [customMode, setCustomMode] = useState(isCustom)
47
+ const [inputValue, setInputValue] = useState(isCustom ? (slippage ?? '') : '')
48
+
49
+ const handleAuto = () => {
50
+ setCustomMode(false)
51
+ setInputValue('')
52
+ setValue('slippage', defaultSlippage)
53
+ }
54
+
55
+ const handlePreset = (preset: string) => {
56
+ setCustomMode(false)
57
+ setValue('slippage', preset)
58
+ }
59
+
60
+ const handleCustom = () => {
61
+ setCustomMode(true)
62
+ const initialValue = slippage || ''
63
+ if (initialValue) {
64
+ setInputValue(initialValue)
65
+ setValue('slippage', initialValue)
66
+ }
67
+ }
68
+
69
+ const formatAndSetSlippage = (value: string, returnInitial = false) => {
70
+ const formattedSlippage = formatSlippage(
71
+ value,
72
+ defaultValue.current,
73
+ returnInitial
74
+ )
75
+ const formattedValue =
76
+ Number(formattedSlippage) === 0 && !returnInitial
77
+ ? '0'
78
+ : formatInputAmount(formattedSlippage, maxFractionDigits, returnInitial)
79
+ const maxLength =
80
+ Number(formattedValue) < 10
81
+ ? maxFractionDigits + 2
82
+ : maxFractionDigits + 3
83
+ const slicedValue = formattedValue.slice(0, maxLength)
84
+ setInputValue(slicedValue)
85
+ setValue('slippage', slicedValue.length ? slicedValue : defaultSlippage)
86
+ }
87
+
88
+ const handleInputUpdate: ChangeEventHandler<HTMLInputElement> = (event) => {
89
+ formatAndSetSlippage(event.target.value, true)
90
+ }
91
+
92
+ const handleInputFocus: FocusEventHandler<HTMLInputElement> = (event) => {
93
+ formatAndSetSlippage(event.target.value)
94
+ }
95
+
96
+ const handleInputBlur: FocusEventHandler<HTMLInputElement> = (event) => {
97
+ formatAndSetSlippage(event.target.value)
98
+ }
99
+
100
+ const slippageWarningText = isSlippageOutsideRecommendedLimits
101
+ ? t('warning.message.slippageOutsideRecommendedLimits')
102
+ : isSlippageUnderRecommendedLimits
103
+ ? t('warning.message.slippageUnderRecommendedLimits')
104
+ : ''
105
+
106
+ return (
107
+ <PageContainer disableGutters>
108
+ <List
109
+ className="long-list"
110
+ ref={listParentRef}
111
+ style={{ height: listHeight, overflow: 'auto' }}
112
+ sx={{
113
+ padding: 1.5,
114
+ }}
115
+ >
116
+ <SettingsListItemButton onClick={handleAuto}>
117
+ <ListItemText
118
+ primary={t('button.auto')}
119
+ secondary={t('settings.slippageAutoDescription')}
120
+ />
121
+ {!customMode && isAuto && <Check color="primary" />}
122
+ </SettingsListItemButton>
123
+ {slippagePresets.map((preset) => (
124
+ <SettingsListItemButton
125
+ key={preset}
126
+ onClick={() => handlePreset(preset)}
127
+ >
128
+ <ListItemText primary={`${preset}%`} />
129
+ {!customMode && slippage === preset && <Check color="primary" />}
130
+ </SettingsListItemButton>
131
+ ))}
132
+ <SettingsListItemButton onClick={handleCustom}>
133
+ <ListItemText primary={t('settings.custom')} />
134
+ {customMode && <Check color="primary" />}
135
+ </SettingsListItemButton>
136
+ <Collapse in={customMode}>
137
+ <Box sx={{ px: 1.5, pt: 1 }}>
138
+ <SlippageInput
139
+ placeholder="0.5"
140
+ inputProps={{ inputMode: 'decimal' }}
141
+ value={inputValue}
142
+ onChange={handleInputUpdate}
143
+ onFocus={handleInputFocus}
144
+ onBlur={handleInputBlur}
145
+ autoComplete="off"
146
+ endAdornment={
147
+ !isAuto && (
148
+ <SlippageClearButton
149
+ size="small"
150
+ onClick={handleAuto}
151
+ aria-label="Clear"
152
+ >
153
+ <CloseRounded fontSize="small" />
154
+ </SlippageClearButton>
155
+ )
156
+ }
157
+ />
158
+ {isSlippageNotRecommended && (
159
+ <SlippageLimitsWarningContainer>
160
+ <WarningRounded color="warning" />
161
+ <Typography
162
+ sx={{
163
+ fontSize: 13,
164
+ fontWeight: 400,
165
+ }}
166
+ >
167
+ {slippageWarningText}
168
+ </Typography>
169
+ </SlippageLimitsWarningContainer>
170
+ )}
171
+ </Box>
172
+ </Collapse>
173
+ </List>
174
+ </PageContainer>
175
+ )
176
+ }
@@ -1,7 +1,7 @@
1
1
  import type { Theme } from '@mui/material'
2
2
  import {
3
3
  Box,
4
- ButtonBase,
4
+ IconButton,
5
5
  InputBase,
6
6
  inputBaseClasses,
7
7
  styled,
@@ -36,30 +36,6 @@ interface SettingsControlProps {
36
36
  selected?: boolean
37
37
  }
38
38
 
39
- export const SettingsDefaultButton: React.FC<
40
- React.ComponentProps<typeof ButtonBase> & SettingsControlProps
41
- > = styled(ButtonBase)<SettingsControlProps>(({ theme, selected }) => {
42
- const settingsControlSelectedStyles = settingsControlSelected(theme)
43
- const selectedStyle = selected
44
- ? {
45
- '&:not(:focus)': {
46
- ...settingsControlSelectedStyles,
47
- },
48
- }
49
- : {}
50
-
51
- return {
52
- height: '100%',
53
- width: '100%',
54
- fontSize: '1rem',
55
- fontWeight: 700,
56
- '&:focus': {
57
- ...settingsControlSelectedStyles,
58
- },
59
- ...selectedStyle,
60
- }
61
- })
62
-
63
39
  export const SettingsCustomInput: React.FC<
64
40
  React.ComponentProps<typeof InputBase> & SettingsControlProps
65
41
  > = styled(InputBase)<SettingsControlProps>(({ theme, selected }) => {
@@ -100,3 +76,34 @@ export const SlippageLimitsWarningContainer: React.FC<
100
76
  gap: theme.spacing(1.25),
101
77
  marginTop: theme.spacing(1.5),
102
78
  }))
79
+
80
+ export const SlippageInput: React.FC<React.ComponentProps<typeof InputBase>> =
81
+ styled(InputBase)(({ theme }) => ({
82
+ minHeight: 44,
83
+ width: '100%',
84
+ backgroundColor: theme.vars.palette.background.default,
85
+ border: '1px solid',
86
+ borderColor: theme.vars.palette.divider,
87
+ borderRadius: theme.vars.shape.borderRadius,
88
+ paddingRight: theme.spacing(1),
89
+ [`.${inputBaseClasses.input}`]: {
90
+ padding: theme.spacing(1.5),
91
+ fontSize: 16,
92
+ fontWeight: 500,
93
+ lineHeight: '20px',
94
+ '&::placeholder': {
95
+ opacity: 1,
96
+ color: theme.vars.palette.text.secondary,
97
+ },
98
+ '&::-webkit-outer-spin-button, &::-webkit-inner-spin-button': {
99
+ display: 'none',
100
+ },
101
+ },
102
+ }))
103
+
104
+ export const SlippageClearButton: React.FC<
105
+ React.ComponentProps<typeof IconButton>
106
+ > = styled(IconButton)(({ theme }) => ({
107
+ padding: theme.spacing(0.5),
108
+ flexShrink: 0,
109
+ }))
@@ -1,82 +1,17 @@
1
1
  import Percent from '@mui/icons-material/Percent'
2
- import WarningRounded from '@mui/icons-material/WarningRounded'
3
- import { Box, Typography } from '@mui/material'
4
- import type { ChangeEventHandler, FocusEventHandler } from 'react'
5
- import { useRef, useState } from 'react'
2
+ import { useNavigate } from '@tanstack/react-router'
6
3
  import { useTranslation } from 'react-i18next'
4
+ import { CardButton } from '../../../components/Card/CardButton.js'
7
5
  import { useSettingMonitor } from '../../../hooks/useSettingMonitor.js'
8
- import { defaultSlippage } from '../../../stores/settings/createSettingsStore.js'
9
6
  import { useSettings } from '../../../stores/settings/useSettings.js'
10
- import { useSettingsActions } from '../../../stores/settings/useSettingsActions.js'
11
- import { formatInputAmount, formatSlippage } from '../../../utils/format.js'
7
+ import { navigationRoutes } from '../../../utils/navigationRoutes.js'
12
8
  import { BadgedValue } from '../SettingsCard/BadgedValue.js'
13
- import { SettingCardExpandable } from '../SettingsCard/SettingCardExpandable.js'
14
- import {
15
- SettingsCustomInput,
16
- SettingsDefaultButton,
17
- SettingsFieldSet,
18
- SlippageLimitsWarningContainer,
19
- } from './SlippageSettings.style.js'
20
-
21
- const defaultSlippageInputValue = '0.5'
22
- const maxFractionDigits = 5
23
9
 
24
10
  export const SlippageSettings: React.FC = () => {
25
11
  const { t } = useTranslation()
26
- const {
27
- isSlippageNotRecommended,
28
- isSlippageUnderRecommendedLimits,
29
- isSlippageOutsideRecommendedLimits,
30
- isSlippageChanged,
31
- } = useSettingMonitor()
12
+ const navigate = useNavigate()
13
+ const { isSlippageNotRecommended, isSlippageChanged } = useSettingMonitor()
32
14
  const { slippage } = useSettings(['slippage'])
33
- const { setValue } = useSettingsActions()
34
- const defaultValue = useRef(slippage)
35
- const [focused, setFocused] = useState<'input' | 'button'>()
36
-
37
- const customInputValue =
38
- !slippage || slippage === defaultSlippage
39
- ? defaultSlippageInputValue
40
- : slippage
41
-
42
- const [inputValue, setInputValue] = useState(customInputValue)
43
-
44
- const handleDefaultClick = () => {
45
- setValue('slippage', defaultSlippage)
46
- }
47
-
48
- const formatAndSetSlippage = (value: string, returnInitial = false) => {
49
- const formattedSlippage = formatSlippage(
50
- value,
51
- defaultValue.current,
52
- returnInitial
53
- )
54
- const formattedValue =
55
- Number(formattedSlippage) === 0 && !returnInitial
56
- ? '0'
57
- : formatInputAmount(formattedSlippage, maxFractionDigits, returnInitial)
58
- const maxLength =
59
- Number(formattedValue) < 10
60
- ? maxFractionDigits + 2
61
- : maxFractionDigits + 3
62
- const slicedValue = formattedValue.slice(0, maxLength)
63
- setInputValue(slicedValue)
64
- setValue('slippage', slicedValue.length ? slicedValue : defaultSlippage)
65
- }
66
-
67
- const handleInputUpdate: ChangeEventHandler<HTMLInputElement> = (event) => {
68
- formatAndSetSlippage(event.target.value, true)
69
- }
70
-
71
- const handleInputFocus: FocusEventHandler<HTMLInputElement> = (event) => {
72
- setFocused('input')
73
- formatAndSetSlippage(event.target.value)
74
- }
75
-
76
- const handleInputBlur: FocusEventHandler<HTMLInputElement> = (event) => {
77
- setFocused(undefined)
78
- formatAndSetSlippage(event.target.value)
79
- }
80
15
 
81
16
  const badgeColor = isSlippageNotRecommended
82
17
  ? 'warning'
@@ -84,68 +19,19 @@ export const SlippageSettings: React.FC = () => {
84
19
  ? 'info'
85
20
  : undefined
86
21
 
87
- const slippageWarningText = isSlippageOutsideRecommendedLimits
88
- ? t('warning.message.slippageOutsideRecommendedLimits')
89
- : isSlippageUnderRecommendedLimits
90
- ? t('warning.message.slippageUnderRecommendedLimits')
91
- : ''
22
+ const handleClick = () => {
23
+ navigate({ to: navigationRoutes.slippage })
24
+ }
92
25
 
93
26
  return (
94
- <SettingCardExpandable
95
- value={
96
- <BadgedValue badgeColor={badgeColor} showBadge={!!badgeColor}>
97
- {slippage ? `${slippage}%` : t('button.auto')}
98
- </BadgedValue>
99
- }
27
+ <CardButton
28
+ onClick={handleClick}
100
29
  icon={<Percent />}
101
30
  title={t('settings.slippage')}
102
31
  >
103
- <Box
104
- sx={{
105
- mt: 1.5,
106
- }}
107
- >
108
- <SettingsFieldSet>
109
- <SettingsDefaultButton
110
- selected={defaultSlippage === slippage && focused !== 'input'}
111
- onFocus={() => {
112
- setFocused('button')
113
- }}
114
- onBlur={() => {
115
- setFocused(undefined)
116
- }}
117
- onClick={handleDefaultClick}
118
- disableRipple
119
- >
120
- {t('button.auto')}
121
- </SettingsDefaultButton>
122
- <SettingsCustomInput
123
- selected={defaultSlippage !== slippage && focused !== 'button'}
124
- placeholder={focused === 'input' ? '' : t('settings.custom')}
125
- inputProps={{
126
- inputMode: 'decimal',
127
- }}
128
- onChange={handleInputUpdate}
129
- onFocus={handleInputFocus}
130
- value={inputValue}
131
- autoComplete="off"
132
- onBlur={handleInputBlur}
133
- />
134
- </SettingsFieldSet>
135
- {isSlippageNotRecommended && (
136
- <SlippageLimitsWarningContainer>
137
- <WarningRounded color="warning" />
138
- <Typography
139
- sx={{
140
- fontSize: 13,
141
- fontWeight: 400,
142
- }}
143
- >
144
- {slippageWarningText}
145
- </Typography>
146
- </SlippageLimitsWarningContainer>
147
- )}
148
- </Box>
149
- </SettingCardExpandable>
32
+ <BadgedValue badgeColor={badgeColor} showBadge={!!badgeColor}>
33
+ {slippage ? `${slippage}%` : t('button.auto')}
34
+ </BadgedValue>
35
+ </CardButton>
150
36
  )
151
37
  }
@@ -5,7 +5,9 @@ import { Tooltip, useColorScheme } from '@mui/material'
5
5
  import { useTranslation } from 'react-i18next'
6
6
  import { CardValue } from '../../components/Card/CardButton.style.js'
7
7
  import { CardTabs, Tab } from '../../components/Tabs/Tabs.style.js'
8
+ import { useWidgetEvents } from '../../hooks/useWidgetEvents.js'
8
9
  import { useWidgetConfig } from '../../providers/WidgetProvider/WidgetProvider.js'
10
+ import { WidgetEvent } from '../../types/events.js'
9
11
  import type { Appearance } from '../../types/widget.js'
10
12
  import { SettingCardExpandable } from './SettingsCard/SettingCardExpandable.js'
11
13
 
@@ -37,6 +39,7 @@ const ThemeTab: React.FC<ThemeTabProps> = ({
37
39
  export const ThemeSettings: React.FC = () => {
38
40
  const { t } = useTranslation()
39
41
  const { mode, setMode } = useColorScheme()
42
+ const widgetEvents = useWidgetEvents()
40
43
  const { hiddenUI } = useWidgetConfig()
41
44
 
42
45
  if (hiddenUI?.appearance) {
@@ -52,6 +55,7 @@ export const ThemeSettings: React.FC = () => {
52
55
  appearance: Appearance
53
56
  ) => {
54
57
  setMode(appearance)
58
+ widgetEvents.emit(WidgetEvent.AppearanceChanged, appearance)
55
59
  }
56
60
 
57
61
  return (
@@ -18,13 +18,23 @@ const formValueKeys: FormFieldNames[] = [
18
18
  'toToken',
19
19
  ]
20
20
 
21
+ // Limit-order params are only mirrored to the URL while the limit tab is
22
+ // active, and stripped when the user leaves it. Unlike the base keys these are
23
+ // presence-gated (not touched-gated) so the URL fully reproduces the limit
24
+ // order — including the boolean/number fields, which are never "falsy-empty".
25
+ const limitFormValueKeys: FormFieldNames[] = [
26
+ 'toAmount',
27
+ 'validUntil',
28
+ 'partiallyFillable',
29
+ ]
30
+
21
31
  export const URLSearchParamsBuilder = () => {
22
32
  const { pathname } = useLocation()
23
33
  const touchedFields = useTouchedFields()
24
- const values = useFieldValues(...formValueKeys)
34
+ const values = useFieldValues(...formValueKeys, ...limitFormValueKeys)
25
35
  const { setSelectedBookmark, addRecentWallet } = useBookmarkActions()
26
36
  const { validateAddress } = useAddressValidation()
27
- const { buildUrl } = useWidgetConfig()
37
+ const { buildUrl, mode } = useWidgetConfig()
28
38
  // Using these methods as trying to use the touchedFields and values above
29
39
  // often has a lag that can effect the widgets initialisation sequence
30
40
  // and accidentally cause values to be wiped from the query string
@@ -85,9 +95,21 @@ export const URLSearchParamsBuilder = () => {
85
95
  url.searchParams.delete(key)
86
96
  }
87
97
  })
98
+ // Limit params live in the URL only while the limit tab is active.
99
+ limitFormValueKeys.forEach((key) => {
100
+ const value = getFieldValues(key)[0]
101
+ // Booleans are always "set" (false is meaningful); strings/numbers use
102
+ // truthiness so an empty toAmount is treated as absent.
103
+ const hasValue = typeof value === 'boolean' ? true : Boolean(value)
104
+ if (mode === 'limit' && hasValue) {
105
+ url.searchParams.set(key, String(value))
106
+ } else {
107
+ url.searchParams.delete(key)
108
+ }
109
+ })
88
110
  url.searchParams.sort()
89
111
  window.history.replaceState(window.history.state, '', url)
90
- }, [pathname, touchedFields, values, isTouched, getFieldValues])
112
+ }, [pathname, touchedFields, values, isTouched, getFieldValues, mode])
91
113
 
92
114
  return null
93
115
  }
@@ -1,6 +1,6 @@
1
1
  import type { StoreApi, UseBoundStore } from 'zustand'
2
2
  import { create } from 'zustand'
3
-
3
+ import { DEFAULT_VALID_UNTIL_SECONDS } from '../../utils/limitOrder.js'
4
4
  import type {
5
5
  DefaultValues,
6
6
  FormFieldArray,
@@ -14,6 +14,9 @@ export const formDefaultValues: DefaultValues = {
14
14
  fromAmount: '',
15
15
  toAmount: '',
16
16
  tokenSearchFilter: '',
17
+ priceInverted: false,
18
+ validUntil: DEFAULT_VALID_UNTIL_SECONDS,
19
+ partiallyFillable: true,
17
20
  }
18
21
 
19
22
  const defaultValueToFormValue = <T>(value: T): FormValueControl<T> => ({
@@ -32,7 +35,8 @@ const valuesToFormValues = (defaultValues: DefaultValues): FormValues => {
32
35
  )
33
36
  }
34
37
 
35
- const isString = (str: any) => typeof str === 'string' || str instanceof String
38
+ const isString = (str: unknown): str is string =>
39
+ typeof str === 'string' || str instanceof String
36
40
 
37
41
  const getUpdatedTouchedFields = (userValues: FormValues) => {
38
42
  return (Object.keys(userValues) as FormFieldNames[]).reduce(
@@ -58,7 +62,7 @@ const mergeDefaultFormValues = (
58
62
  ),
59
63
  isDirty: !!(userValues[key]?.isDirty || defaultValues[key]?.isTouched),
60
64
  value:
61
- userValues[key]?.value || Number.isFinite(userValues[key]?.value)
65
+ userValues[key]?.value !== undefined && userValues[key]?.value !== ''
62
66
  ? userValues[key]?.value
63
67
  : defaultValues[key]?.value,
64
68
  }
@@ -41,6 +41,17 @@ export const getDefaultValuesFromQueryString = ({
41
41
  ...(Number.isFinite(Number.parseFloat(searchParams.fromAmount))
42
42
  ? { fromAmount: formatInputAmount(searchParams.fromAmount) }
43
43
  : {}),
44
+ ...(Number.isFinite(Number.parseFloat(searchParams.toAmount))
45
+ ? { toAmount: formatInputAmount(searchParams.toAmount) }
46
+ : {}),
47
+ // Limit-order params (written only while on the limit tab).
48
+ ...(Number.isFinite(Number.parseInt(searchParams.validUntil, 10))
49
+ ? { validUntil: Number.parseInt(searchParams.validUntil, 10) }
50
+ : {}),
51
+ ...(searchParams.partiallyFillable === 'true' ||
52
+ searchParams.partiallyFillable === 'false'
53
+ ? { partiallyFillable: searchParams.partiallyFillable === 'true' }
54
+ : {}),
44
55
  ...(includeToAddress && searchParams.toAddress
45
56
  ? { toAddress: searchParams.toAddress }
46
57
  : {}),
@@ -14,9 +14,22 @@ export interface DefaultFieldValues {
14
14
  export interface DefaultValues extends DefaultFieldValues {
15
15
  contractCalls?: ContractCall[]
16
16
  tokenSearchFilter: string
17
+ /** Limit-order: when true, the price is displayed/entered inverted. */
18
+ priceInverted: boolean
19
+ /** Limit-order: order validity as a duration in seconds. */
20
+ validUntil: number
21
+ /** Limit-order: whether the order may be partially filled. */
22
+ partiallyFillable: boolean
23
+ /** Limit-order: id of the route the user picked. */
24
+ selectedRouteId?: string
17
25
  }
18
26
 
19
- export type GenericFormValue = string | number | ContractCall[] | undefined
27
+ export type GenericFormValue =
28
+ | string
29
+ | number
30
+ | boolean
31
+ | ContractCall[]
32
+ | undefined
20
33
  export interface FormValueControl<T> {
21
34
  isTouched: boolean
22
35
  isDirty: boolean
@@ -6,5 +6,6 @@ export const useClearAmountFields = (): (() => void) => {
6
6
  return useCallback(() => {
7
7
  setFieldValue('fromAmount', '')
8
8
  setFieldValue('toAmount', '')
9
+ setFieldValue('selectedRouteId', undefined)
9
10
  }, [setFieldValue])
10
11
  }
@@ -1,7 +1,9 @@
1
1
  import type { StoreApi, UseBoundStore } from 'zustand'
2
2
  import type {
3
+ DefaultUI,
3
4
  ModeOptions,
4
5
  NavigationTabKey,
6
+ RequiredUIConfig,
5
7
  WidgetMode,
6
8
  WidgetVariant,
7
9
  } from '../../types/widget.js'
@@ -14,6 +16,10 @@ export interface NavigationTab {
14
16
  /** When omitted, the tab inherits `config.mode`. */
15
17
  mode?: WidgetMode
16
18
  modeOptions?: ModeOptions
19
+ /** Layered over `config.defaultUI` while the tab is active. */
20
+ defaultUI?: DefaultUI
21
+ /** Layered over `config.requiredUI` while the tab is active. */
22
+ requiredUI?: RequiredUIConfig
17
23
  }
18
24
 
19
25
  export interface NavigationTabsState {
@@ -21,8 +21,10 @@ import type { NavigationTabsState, NavigationTabsStore } from './types.js'
21
21
  import {
22
22
  getInitialActiveTab,
23
23
  getNavigationTabKeys,
24
+ getTabDefaultUI,
24
25
  getTabMode,
25
26
  getTabModeOptions,
27
+ getTabRequiredUI,
26
28
  getTabSplitMode,
27
29
  getTabVariant,
28
30
  } from './utils.js'
@@ -49,9 +51,10 @@ export function NavigationTabsStoreProvider({
49
51
  signatureRef.current = signature
50
52
  }
51
53
 
52
- // Override the widget config with the active tab's mode, variant and
53
- // modeOptions (each falling back to the config), so the rest of the widget
54
- // reads tab-driven config transparently. No-op when there is no active tab.
54
+ // Override the widget config with the active tab's mode, variant,
55
+ // modeOptions, defaultUI and requiredUI (each falling back to the config), so
56
+ // the rest of the widget reads tab-driven config transparently. No-op when
57
+ // there is no active tab.
55
58
  const widgetConfig = useWidgetConfig()
56
59
  const activeTab = store((state) => state.activeTab)
57
60
  const tabConfig = useMemo(() => {
@@ -63,6 +66,8 @@ export function NavigationTabsStoreProvider({
63
66
  mode: getTabMode(widgetConfig, activeTab),
64
67
  variant: getTabVariant(widgetConfig, activeTab),
65
68
  modeOptions: getTabModeOptions(widgetConfig, activeTab),
69
+ defaultUI: getTabDefaultUI(widgetConfig, activeTab),
70
+ requiredUI: getTabRequiredUI(widgetConfig, activeTab),
66
71
  }
67
72
  }, [widgetConfig, activeTab])
68
73