@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
@@ -0,0 +1,80 @@
1
+ import { useWidgetConfig } from "../../providers/WidgetProvider/WidgetProvider.js";
2
+ import { useAvailableChains } from "../../hooks/useAvailableChains.js";
3
+ import { useFieldActions } from "../../stores/form/useFieldActions.js";
4
+ import { FormKeyHelper } from "../../stores/form/types.js";
5
+ import { useFieldValues } from "../../stores/form/useFieldValues.js";
6
+ import { useGasRecommendation } from "../../hooks/useGasRecommendation.js";
7
+ import { useTokenAddressBalance } from "../../hooks/useTokenAddressBalance.js";
8
+ import { useLinkedLimitFields } from "../../hooks/useLinkedLimitFields.js";
9
+ import { Chip, ChipContainer } from "./PercentageChips.style.js";
10
+ import { formatUnits } from "@lifi/sdk";
11
+ import { memo } from "react";
12
+ import { jsx, jsxs } from "react/jsx-runtime";
13
+ import { useTranslation } from "react-i18next";
14
+ import { useAccount } from "@lifi/wallet-management";
15
+ //#region src/components/AmountInputCard/PercentageChips.tsx
16
+ const PercentageChips = memo(({ formType }) => {
17
+ const { t } = useTranslation();
18
+ const { getChainById } = useAvailableChains();
19
+ const { setFieldValue } = useFieldActions();
20
+ const { disabledUI, mode } = useWidgetConfig();
21
+ const { setSendAmount } = useLinkedLimitFields();
22
+ const amountKey = FormKeyHelper.getAmountKey(formType);
23
+ const isDisabled = !!disabledUI?.[amountKey];
24
+ const [chainId, tokenAddress] = useFieldValues(FormKeyHelper.getChainKey(formType), FormKeyHelper.getTokenKey(formType));
25
+ const chain = getChainById(chainId);
26
+ const { account } = useAccount({ chainType: chain?.chainType });
27
+ const { data } = useGasRecommendation(chainId);
28
+ const { token } = useTokenAddressBalance(chainId, tokenAddress);
29
+ const getMaxAmount = () => {
30
+ if (!token?.amount) return 0n;
31
+ let maxAmount = token.amount;
32
+ if (chain?.nativeToken.address === tokenAddress && data?.recommended) {
33
+ const recommendedAmount = BigInt(data.recommended.amount);
34
+ if (token.amount > recommendedAmount) maxAmount = token.amount - recommendedAmount;
35
+ }
36
+ return maxAmount;
37
+ };
38
+ const applyAmount = (value) => {
39
+ if (mode === "limit") setSendAmount(value);
40
+ else setFieldValue(amountKey, value, { isTouched: true });
41
+ };
42
+ const handlePercentage = (percentage) => {
43
+ const maxAmount = getMaxAmount();
44
+ if (maxAmount && token?.decimals) {
45
+ const percentageAmount = maxAmount * BigInt(percentage) / 100n;
46
+ applyAmount(formatUnits(percentageAmount, token.decimals));
47
+ }
48
+ };
49
+ const handleMax = () => {
50
+ const maxAmount = getMaxAmount();
51
+ if (maxAmount && token?.decimals) applyAmount(formatUnits(maxAmount, token.decimals));
52
+ };
53
+ if (formType !== "from" || isDisabled || !token || !account?.isConnected) return null;
54
+ return /* @__PURE__ */ jsxs(ChipContainer, { children: [
55
+ /* @__PURE__ */ jsx(Chip, {
56
+ "data-delay": "0",
57
+ onClick: () => handlePercentage(25),
58
+ children: "25%"
59
+ }),
60
+ /* @__PURE__ */ jsx(Chip, {
61
+ "data-delay": "1",
62
+ onClick: () => handlePercentage(50),
63
+ children: "50%"
64
+ }),
65
+ /* @__PURE__ */ jsx(Chip, {
66
+ "data-delay": "2",
67
+ onClick: () => handlePercentage(75),
68
+ children: "75%"
69
+ }),
70
+ /* @__PURE__ */ jsx(Chip, {
71
+ "data-delay": "3",
72
+ onClick: handleMax,
73
+ children: t("button.max")
74
+ })
75
+ ] });
76
+ });
77
+ //#endregion
78
+ export { PercentageChips };
79
+
80
+ //# sourceMappingURL=PercentageChips.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PercentageChips.js","names":[],"sources":["../../../../src/components/AmountInputCard/PercentageChips.tsx"],"sourcesContent":["import { formatUnits } from '@lifi/sdk'\nimport { useAccount } from '@lifi/wallet-management'\nimport { type JSX, memo } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { useAvailableChains } from '../../hooks/useAvailableChains.js'\nimport { useGasRecommendation } from '../../hooks/useGasRecommendation.js'\nimport { useLinkedLimitFields } from '../../hooks/useLinkedLimitFields.js'\nimport { useTokenAddressBalance } from '../../hooks/useTokenAddressBalance.js'\nimport { useWidgetConfig } from '../../providers/WidgetProvider/WidgetProvider.js'\nimport type { FormTypeProps } from '../../stores/form/types.js'\nimport { FormKeyHelper } from '../../stores/form/types.js'\nimport { useFieldActions } from '../../stores/form/useFieldActions.js'\nimport { useFieldValues } from '../../stores/form/useFieldValues.js'\nimport type { DisabledUIConfig } from '../../types/widget.js'\nimport { Chip, ChipContainer } from './PercentageChips.style.js'\n\nexport const PercentageChips: React.NamedExoticComponent<FormTypeProps> = memo(\n ({ formType }: FormTypeProps): JSX.Element | null => {\n const { t } = useTranslation()\n const { getChainById } = useAvailableChains()\n const { setFieldValue } = useFieldActions()\n const { disabledUI, mode } = useWidgetConfig()\n const { setSendAmount } = useLinkedLimitFields()\n\n const amountKey = FormKeyHelper.getAmountKey(formType)\n const isDisabled = !!disabledUI?.[amountKey as keyof DisabledUIConfig]\n\n const [chainId, tokenAddress] = useFieldValues(\n FormKeyHelper.getChainKey(formType),\n FormKeyHelper.getTokenKey(formType)\n )\n\n const chain = getChainById(chainId)\n const { account } = useAccount({ chainType: chain?.chainType })\n\n const { data } = useGasRecommendation(chainId)\n const { token } = useTokenAddressBalance(chainId, tokenAddress)\n\n const getMaxAmount = (): bigint => {\n if (!token?.amount) {\n return 0n\n }\n let maxAmount = token.amount\n if (chain?.nativeToken.address === tokenAddress && data?.recommended) {\n const recommendedAmount = BigInt(data.recommended.amount)\n if (token.amount > recommendedAmount) {\n maxAmount = token.amount - recommendedAmount\n }\n }\n return maxAmount\n }\n\n // In limit mode the send amount must flow through the linked-field\n // derivation so the receive amount recomputes; otherwise it is a plain\n // form-field write.\n const applyAmount = (value: string): void => {\n if (mode === 'limit') {\n setSendAmount(value)\n } else {\n setFieldValue(amountKey, value, { isTouched: true })\n }\n }\n\n const handlePercentage = (percentage: number): void => {\n const maxAmount = getMaxAmount()\n if (maxAmount && token?.decimals) {\n const percentageAmount = (maxAmount * BigInt(percentage)) / 100n\n applyAmount(formatUnits(percentageAmount, token.decimals))\n }\n }\n\n const handleMax = (): void => {\n const maxAmount = getMaxAmount()\n if (maxAmount && token?.decimals) {\n applyAmount(formatUnits(maxAmount, token.decimals))\n }\n }\n\n if (formType !== 'from' || isDisabled || !token || !account?.isConnected) {\n return null\n }\n\n return (\n <ChipContainer>\n <Chip data-delay=\"0\" onClick={() => handlePercentage(25)}>\n 25%\n </Chip>\n <Chip data-delay=\"1\" onClick={() => handlePercentage(50)}>\n 50%\n </Chip>\n <Chip data-delay=\"2\" onClick={() => handlePercentage(75)}>\n 75%\n </Chip>\n <Chip data-delay=\"3\" onClick={handleMax}>\n {t('button.max')}\n </Chip>\n </ChipContainer>\n )\n }\n)\n"],"mappings":";;;;;;;;;;;;;;;AAgBA,MAAa,kBAA6D,MACvE,EAAE,eAAkD;CACnD,MAAM,EAAE,MAAM,eAAe;CAC7B,MAAM,EAAE,iBAAiB,mBAAmB;CAC5C,MAAM,EAAE,kBAAkB,gBAAgB;CAC1C,MAAM,EAAE,YAAY,SAAS,gBAAgB;CAC7C,MAAM,EAAE,kBAAkB,qBAAqB;CAE/C,MAAM,YAAY,cAAc,aAAa,QAAQ;CACrD,MAAM,aAAa,CAAC,CAAC,aAAa;CAElC,MAAM,CAAC,SAAS,gBAAgB,eAC9B,cAAc,YAAY,QAAQ,GAClC,cAAc,YAAY,QAAQ,CACpC;CAEA,MAAM,QAAQ,aAAa,OAAO;CAClC,MAAM,EAAE,YAAY,WAAW,EAAE,WAAW,OAAO,UAAU,CAAC;CAE9D,MAAM,EAAE,SAAS,qBAAqB,OAAO;CAC7C,MAAM,EAAE,UAAU,uBAAuB,SAAS,YAAY;CAE9D,MAAM,qBAA6B;EACjC,IAAI,CAAC,OAAO,QACV,OAAO;EAET,IAAI,YAAY,MAAM;EACtB,IAAI,OAAO,YAAY,YAAY,gBAAgB,MAAM,aAAa;GACpE,MAAM,oBAAoB,OAAO,KAAK,YAAY,MAAM;GACxD,IAAI,MAAM,SAAS,mBACjB,YAAY,MAAM,SAAS;EAE/B;EACA,OAAO;CACT;CAKA,MAAM,eAAe,UAAwB;EAC3C,IAAI,SAAS,SACX,cAAc,KAAK;OAEnB,cAAc,WAAW,OAAO,EAAE,WAAW,KAAK,CAAC;CAEvD;CAEA,MAAM,oBAAoB,eAA6B;EACrD,MAAM,YAAY,aAAa;EAC/B,IAAI,aAAa,OAAO,UAAU;GAChC,MAAM,mBAAoB,YAAY,OAAO,UAAU,IAAK;GAC5D,YAAY,YAAY,kBAAkB,MAAM,QAAQ,CAAC;EAC3D;CACF;CAEA,MAAM,kBAAwB;EAC5B,MAAM,YAAY,aAAa;EAC/B,IAAI,aAAa,OAAO,UACtB,YAAY,YAAY,WAAW,MAAM,QAAQ,CAAC;CAEtD;CAEA,IAAI,aAAa,UAAU,cAAc,CAAC,SAAS,CAAC,SAAS,aAC3D,OAAO;CAGT,OACE,qBAAC,eAAD,EAAA,UAAA;EACE,oBAAC,MAAD;GAAM,cAAW;GAAI,eAAe,iBAAiB,EAAE;aAAG;EAEpD,CAAA;EACN,oBAAC,MAAD;GAAM,cAAW;GAAI,eAAe,iBAAiB,EAAE;aAAG;EAEpD,CAAA;EACN,oBAAC,MAAD;GAAM,cAAW;GAAI,eAAe,iBAAiB,EAAE;aAAG;EAEpD,CAAA;EACN,oBAAC,MAAD;GAAM,cAAW;GAAI,SAAS;aAC3B,EAAE,YAAY;EACX,CAAA;CACO,EAAA,CAAA;AAEnB,CACF"}
@@ -0,0 +1,43 @@
1
+ import { Box, Button, styled } from "@mui/material";
2
+ import { cardClasses } from "@mui/material/Card";
3
+ //#region src/components/AmountInputCard/PercentageChips.style.tsx
4
+ const chipCount = 4;
5
+ const staggerStepMs = 25;
6
+ const revealStagger = Object.fromEntries(Array.from({ length: chipCount }, (_, index) => [`&[data-delay="${index}"]`, {
7
+ [`.${cardClasses.root}:hover &`]: {
8
+ opacity: 1,
9
+ transform: "scale(1) translateX(0)",
10
+ pointerEvents: "auto",
11
+ transitionDelay: `${(chipCount - 1 - index) * staggerStepMs}ms`
12
+ },
13
+ [`.${cardClasses.root}:not(:hover) &`]: { transitionDelay: `${index * staggerStepMs}ms` }
14
+ }]));
15
+ const ChipContainer = styled(Box)(({ theme }) => ({
16
+ display: "flex",
17
+ gap: theme.spacing(.5)
18
+ }));
19
+ const Chip = styled(Button)(({ theme }) => ({
20
+ minWidth: 0,
21
+ padding: theme.spacing(.5, 1.5),
22
+ fontSize: 12,
23
+ fontWeight: 700,
24
+ lineHeight: 1.3334,
25
+ backgroundColor: `color-mix(in srgb, ${theme.vars.palette.common.onBackground} 4%, transparent)`,
26
+ color: theme.vars.palette.text.primary,
27
+ transition: "opacity 200ms cubic-bezier(0.4, 0, 0.2, 1), transform 200ms cubic-bezier(0.4, 0, 0.2, 1), background-color 200ms ease",
28
+ "&:hover": { backgroundColor: `color-mix(in srgb, ${theme.vars.palette.common.onBackground} 8%, transparent)` },
29
+ "@media (hover: hover)": {
30
+ opacity: 0,
31
+ transform: "scale(0.85) translateX(10px)",
32
+ pointerEvents: "none",
33
+ ...revealStagger,
34
+ "@media (prefers-reduced-motion: reduce)": {
35
+ transform: "none !important",
36
+ transitionDelay: "0ms !important"
37
+ }
38
+ }
39
+ }));
40
+ //#endregion
41
+ export { Chip, ChipContainer };
42
+
43
+ //# sourceMappingURL=PercentageChips.style.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PercentageChips.style.js","names":[],"sources":["../../../../src/components/AmountInputCard/PercentageChips.style.tsx"],"sourcesContent":["import { Box, Button, styled } from '@mui/material'\nimport { cardClasses } from '@mui/material/Card'\nimport type React from 'react'\n\nconst chipCount = 4\nconst staggerStepMs = 25\n\nconst revealStagger = Object.fromEntries(\n Array.from({ length: chipCount }, (_, index) => [\n `&[data-delay=\"${index}\"]`,\n {\n [`.${cardClasses.root}:hover &`]: {\n opacity: 1,\n transform: 'scale(1) translateX(0)',\n pointerEvents: 'auto',\n transitionDelay: `${(chipCount - 1 - index) * staggerStepMs}ms`,\n },\n [`.${cardClasses.root}:not(:hover) &`]: {\n transitionDelay: `${index * staggerStepMs}ms`,\n },\n },\n ])\n)\n\nexport const ChipContainer: React.FC<React.ComponentProps<typeof Box>> = styled(\n Box\n)(({ theme }) => ({\n display: 'flex',\n gap: theme.spacing(0.5),\n}))\n\nexport const Chip: React.FC<React.ComponentProps<typeof Button>> = styled(\n Button\n)(({ theme }) => ({\n minWidth: 0,\n padding: theme.spacing(0.5, 1.5),\n fontSize: 12,\n fontWeight: 700,\n lineHeight: 1.3334,\n backgroundColor: `color-mix(in srgb, ${theme.vars.palette.common.onBackground} 4%, transparent)`,\n color: theme.vars.palette.text.primary,\n transition:\n 'opacity 200ms cubic-bezier(0.4, 0, 0.2, 1), transform 200ms cubic-bezier(0.4, 0, 0.2, 1), background-color 200ms ease',\n '&:hover': {\n backgroundColor: `color-mix(in srgb, ${theme.vars.palette.common.onBackground} 8%, transparent)`,\n },\n // Reveal-on-hover — only on devices that support hover. On touch there is no\n // hover, so the chips stay permanently visible (the default above) instead of\n // becoming an unreachable dead row.\n '@media (hover: hover)': {\n opacity: 0,\n transform: 'scale(0.85) translateX(10px)',\n pointerEvents: 'none',\n ...revealStagger,\n '@media (prefers-reduced-motion: reduce)': {\n transform: 'none !important',\n transitionDelay: '0ms !important',\n },\n },\n}))\n"],"mappings":";;;AAIA,MAAM,YAAY;AAClB,MAAM,gBAAgB;AAEtB,MAAM,gBAAgB,OAAO,YAC3B,MAAM,KAAK,EAAE,QAAQ,UAAU,IAAI,GAAG,UAAU,CAC9C,iBAAiB,MAAM,KACvB;EACG,IAAI,YAAY,KAAK,YAAY;EAChC,SAAS;EACT,WAAW;EACX,eAAe;EACf,iBAAiB,IAAI,YAAY,IAAI,SAAS,cAAc;CAC9D;EACC,IAAI,YAAY,KAAK,kBAAkB,EACtC,iBAAiB,GAAG,QAAQ,cAAc,IAC5C;AACF,CACF,CAAC,CACH;AAEA,MAAa,gBAA4D,OACvE,GACF,CAAC,EAAE,EAAE,aAAa;CAChB,SAAS;CACT,KAAK,MAAM,QAAQ,EAAG;AACxB,EAAE;AAEF,MAAa,OAAsD,OACjE,MACF,CAAC,EAAE,EAAE,aAAa;CAChB,UAAU;CACV,SAAS,MAAM,QAAQ,IAAK,GAAG;CAC/B,UAAU;CACV,YAAY;CACZ,YAAY;CACZ,iBAAiB,sBAAsB,MAAM,KAAK,QAAQ,OAAO,aAAa;CAC9E,OAAO,MAAM,KAAK,QAAQ,KAAK;CAC/B,YACE;CACF,WAAW,EACT,iBAAiB,sBAAsB,MAAM,KAAK,QAAQ,OAAO,aAAa,mBAChF;CAIA,yBAAyB;EACvB,SAAS;EACT,WAAW;EACX,eAAe;EACf,GAAG;EACH,2CAA2C;GACzC,WAAW;GACX,iBAAiB;EACnB;CACF;AACF,EAAE"}
@@ -0,0 +1,137 @@
1
+ import { useWidgetConfig } from "../../providers/WidgetProvider/WidgetProvider.js";
2
+ import { formatTokenAmount, formatTokenPrice } from "../../utils/format.js";
3
+ import { useFieldValues } from "../../stores/form/useFieldValues.js";
4
+ import { useRoutes } from "../../hooks/useRoutes.js";
5
+ import { ProgressToNextUpdate } from "../ProgressToNextUpdate.js";
6
+ import { getPriceImpact } from "../../utils/getPriceImpact.js";
7
+ import { useInputModeStore } from "../../stores/inputMode/useInputModeStore.js";
8
+ import { fitInputText } from "../../utils/input.js";
9
+ import { CardTitle } from "../Card/CardTitle.js";
10
+ import { AmountCard, AmountDisplay, CardBodyRow, CardFooterRow, CardHeaderRow, FooterText, ToggleButton } from "./AmountInputCard.style.js";
11
+ import { TokenPillButton } from "../TokenPillButton/TokenPillButton.js";
12
+ import { useLayoutEffect, useRef } from "react";
13
+ import { Box, Skeleton, Tooltip } from "@mui/material";
14
+ import { jsx, jsxs } from "react/jsx-runtime";
15
+ import { useTranslation } from "react-i18next";
16
+ import InfoOutlined from "@mui/icons-material/InfoOutlined";
17
+ import SwapVertIcon from "@mui/icons-material/SwapVert";
18
+ //#region src/components/AmountInputCard/ReceiveAmountCard.tsx
19
+ const formType = "to";
20
+ const ReceiveAmountCardLayout = ({ cardProps, title, headerEnd, amount, footerStart, footerEnd }) => /* @__PURE__ */ jsxs(AmountCard, {
21
+ ...cardProps,
22
+ formType,
23
+ children: [
24
+ /* @__PURE__ */ jsxs(CardHeaderRow, { children: [/* @__PURE__ */ jsx(CardTitle, {
25
+ sx: { padding: 0 },
26
+ children: title
27
+ }), headerEnd] }),
28
+ /* @__PURE__ */ jsxs(CardBodyRow, { children: [amount, /* @__PURE__ */ jsx(TokenPillButton, { formType })] }),
29
+ /* @__PURE__ */ jsxs(CardFooterRow, { children: [footerStart, footerEnd] })
30
+ ]
31
+ });
32
+ const PriceImpactBadge = ({ priceImpact }) => {
33
+ const { t } = useTranslation();
34
+ if (priceImpact === void 0) return null;
35
+ return /* @__PURE__ */ jsxs(Box, {
36
+ sx: {
37
+ display: "flex",
38
+ alignItems: "center",
39
+ gap: .5
40
+ },
41
+ children: [/* @__PURE__ */ jsx(FooterText, { children: t("format.percent", {
42
+ value: priceImpact,
43
+ usePlusSign: true
44
+ }) }), /* @__PURE__ */ jsx(Tooltip, {
45
+ title: t("tooltip.priceImpact"),
46
+ children: /* @__PURE__ */ jsx(InfoOutlined, { sx: {
47
+ fontSize: 16,
48
+ color: "text.secondary",
49
+ cursor: "help"
50
+ } })
51
+ })]
52
+ });
53
+ };
54
+ const useReceivePriceImpact = (route) => {
55
+ const { hiddenUI } = useWidgetConfig();
56
+ if (!route || hiddenUI?.routeCardPriceImpact) return;
57
+ return getPriceImpact({
58
+ fromAmount: BigInt(route.fromAmount),
59
+ toAmount: BigInt(route.toAmount),
60
+ fromToken: route.fromToken,
61
+ toToken: route.toToken
62
+ });
63
+ };
64
+ /**
65
+ * The receive amount is a read-only quote derived from the best route, with a
66
+ * fiat/token toggle and price impact in the footer. In limit mode the amount is
67
+ * still the quote for the current limit (the limit price drives it via the
68
+ * dedicated price card); it is never edited here, and the header shows the
69
+ * refetch progress indicator with a "Buy" title.
70
+ */
71
+ const ReceiveAmountCard = (props) => {
72
+ const { t } = useTranslation();
73
+ const amountRef = useRef(null);
74
+ const { inputMode, toggleInputMode } = useInputModeStore();
75
+ const showFiat = inputMode[formType] === "price";
76
+ const { hiddenUI, mode } = useWidgetConfig();
77
+ const isLimit = mode === "limit";
78
+ const [selectedRouteId] = useFieldValues("selectedRouteId");
79
+ const { routes, isFetching, dataUpdatedAt, refetchTime, refetch } = useRoutes();
80
+ const route = routes?.find((r) => r.id === selectedRouteId) ?? routes?.[0];
81
+ const priceImpact = useReceivePriceImpact(route);
82
+ const showPriceImpact = !hiddenUI?.routeCardPriceImpact;
83
+ const receiveAmount = route?.toAmount ? formatTokenAmount(BigInt(route.toAmount), route.toToken.decimals) : void 0;
84
+ const showSkeleton = isFetching && !receiveAmount;
85
+ const fiatValue = receiveAmount && route?.toToken.priceUSD ? formatTokenPrice(receiveAmount, route.toToken.priceUSD) : 0;
86
+ const canToggle = !!receiveAmount && !!route?.toToken.priceUSD;
87
+ const mainDisplay = showFiat ? t("format.currency", { value: fiatValue }) : t("format.tokenAmount", { value: receiveAmount || "0" });
88
+ const footerDisplay = showFiat ? t("format.tokenAmount", { value: receiveAmount || "0" }) : t("format.currency", { value: fiatValue });
89
+ useLayoutEffect(() => {
90
+ if (amountRef.current) fitInputText(30, 20, amountRef.current);
91
+ }, [mainDisplay]);
92
+ return /* @__PURE__ */ jsx(ReceiveAmountCardLayout, {
93
+ cardProps: props,
94
+ title: isLimit ? t("header.buy") : t("header.receive"),
95
+ headerEnd: isLimit && (isFetching || routes?.length) ? /* @__PURE__ */ jsx(ProgressToNextUpdate, {
96
+ updatedAt: dataUpdatedAt || Date.now(),
97
+ timeToUpdate: refetchTime,
98
+ isLoading: isFetching,
99
+ onClick: () => refetch(),
100
+ sx: { padding: 0 }
101
+ }) : void 0,
102
+ amount: showSkeleton ? /* @__PURE__ */ jsx(Skeleton, {
103
+ variant: "rounded",
104
+ height: 32,
105
+ sx: { flex: 1 }
106
+ }) : /* @__PURE__ */ jsx(AmountDisplay, {
107
+ ref: amountRef,
108
+ sx: {
109
+ color: "text.primary",
110
+ opacity: receiveAmount ? 1 : .5
111
+ },
112
+ children: mainDisplay
113
+ }),
114
+ footerStart: showSkeleton ? /* @__PURE__ */ jsx(Skeleton, {
115
+ variant: "text",
116
+ width: 72,
117
+ sx: { fontSize: 12 }
118
+ }) : /* @__PURE__ */ jsxs(ToggleButton, {
119
+ clickable: canToggle,
120
+ onClick: canToggle ? () => toggleInputMode(formType) : void 0,
121
+ "aria-label": canToggle ? "Toggle fiat value" : void 0,
122
+ children: [/* @__PURE__ */ jsx(FooterText, { children: footerDisplay }), canToggle ? /* @__PURE__ */ jsx(SwapVertIcon, { sx: {
123
+ fontSize: 14,
124
+ color: "text.secondary"
125
+ } }) : null]
126
+ }),
127
+ footerEnd: showSkeleton ? showPriceImpact ? /* @__PURE__ */ jsx(Skeleton, {
128
+ variant: "text",
129
+ width: 56,
130
+ sx: { fontSize: 12 }
131
+ }) : null : /* @__PURE__ */ jsx(PriceImpactBadge, { priceImpact })
132
+ });
133
+ };
134
+ //#endregion
135
+ export { ReceiveAmountCard };
136
+
137
+ //# sourceMappingURL=ReceiveAmountCard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReceiveAmountCard.js","names":["InfoOutlinedIcon"],"sources":["../../../../src/components/AmountInputCard/ReceiveAmountCard.tsx"],"sourcesContent":["import type { Route } from '@lifi/sdk'\nimport InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'\nimport SwapVertIcon from '@mui/icons-material/SwapVert'\nimport type { CardProps } from '@mui/material'\nimport { Box, Skeleton, Tooltip } from '@mui/material'\nimport { type JSX, type ReactNode, useLayoutEffect, useRef } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { useRoutes } from '../../hooks/useRoutes.js'\nimport { useWidgetConfig } from '../../providers/WidgetProvider/WidgetProvider.js'\nimport { useFieldValues } from '../../stores/form/useFieldValues.js'\nimport { useInputModeStore } from '../../stores/inputMode/useInputModeStore.js'\nimport { formatTokenAmount, formatTokenPrice } from '../../utils/format.js'\nimport { getPriceImpact } from '../../utils/getPriceImpact.js'\nimport { fitInputText } from '../../utils/input.js'\nimport { CardTitle } from '../Card/CardTitle.js'\nimport { ProgressToNextUpdate } from '../ProgressToNextUpdate.js'\nimport { TokenPillButton } from '../TokenPillButton/TokenPillButton.js'\nimport {\n AmountCard,\n AmountDisplay,\n amountHeight,\n CardBodyRow,\n CardFooterRow,\n CardHeaderRow,\n FooterText,\n footerFontSize,\n maxInputFontSize,\n minInputFontSize,\n ToggleButton,\n} from './AmountInputCard.style.js'\n\nconst formType = 'to' as const\n\ntype ReceiveCardProps = CardProps & { mask?: boolean }\n\nconst ReceiveAmountCardLayout = ({\n cardProps,\n title,\n headerEnd,\n amount,\n footerStart,\n footerEnd,\n}: {\n cardProps: ReceiveCardProps\n title: string\n headerEnd?: ReactNode\n amount: ReactNode\n footerStart: ReactNode\n footerEnd?: ReactNode\n}): JSX.Element => (\n <AmountCard {...cardProps} formType={formType}>\n <CardHeaderRow>\n <CardTitle sx={{ padding: 0 }}>{title}</CardTitle>\n {headerEnd}\n </CardHeaderRow>\n <CardBodyRow>\n {amount}\n <TokenPillButton formType={formType} />\n </CardBodyRow>\n <CardFooterRow>\n {footerStart}\n {footerEnd}\n </CardFooterRow>\n </AmountCard>\n)\n\nconst PriceImpactBadge = ({\n priceImpact,\n}: {\n priceImpact: number | undefined\n}): JSX.Element | null => {\n const { t } = useTranslation()\n if (priceImpact === undefined) {\n return null\n }\n return (\n <Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>\n <FooterText>\n {t('format.percent', { value: priceImpact, usePlusSign: true })}\n </FooterText>\n <Tooltip title={t('tooltip.priceImpact')}>\n <InfoOutlinedIcon\n sx={{ fontSize: 16, color: 'text.secondary', cursor: 'help' }}\n />\n </Tooltip>\n </Box>\n )\n}\n\nconst useReceivePriceImpact = (\n route: Route | undefined\n): number | undefined => {\n const { hiddenUI } = useWidgetConfig()\n if (!route || hiddenUI?.routeCardPriceImpact) {\n return undefined\n }\n return getPriceImpact({\n fromAmount: BigInt(route.fromAmount),\n toAmount: BigInt(route.toAmount),\n fromToken: route.fromToken,\n toToken: route.toToken,\n })\n}\n\n/**\n * The receive amount is a read-only quote derived from the best route, with a\n * fiat/token toggle and price impact in the footer. In limit mode the amount is\n * still the quote for the current limit (the limit price drives it via the\n * dedicated price card); it is never edited here, and the header shows the\n * refetch progress indicator with a \"Buy\" title.\n */\nexport const ReceiveAmountCard: React.FC<ReceiveCardProps> = (\n props\n): JSX.Element => {\n const { t } = useTranslation()\n const amountRef = useRef<HTMLSpanElement>(null)\n const { inputMode, toggleInputMode } = useInputModeStore()\n const showFiat = inputMode[formType] === 'price'\n const { hiddenUI, mode } = useWidgetConfig()\n const isLimit = mode === 'limit'\n\n const [selectedRouteId] = useFieldValues('selectedRouteId')\n const { routes, isFetching, dataUpdatedAt, refetchTime, refetch } =\n useRoutes()\n // Reflect the route the user selected in the routes list (falling back to the\n // best route), so the buy amount matches the highlighted provider card.\n const route = routes?.find((r) => r.id === selectedRouteId) ?? routes?.[0]\n const priceImpact = useReceivePriceImpact(route)\n const showPriceImpact = !hiddenUI?.routeCardPriceImpact\n\n const receiveAmount = route?.toAmount\n ? formatTokenAmount(BigInt(route.toAmount), route.toToken.decimals)\n : undefined\n // Only show skeletons on the initial fetch.\n const showSkeleton = isFetching && !receiveAmount\n\n const fiatValue =\n receiveAmount && route?.toToken.priceUSD\n ? formatTokenPrice(receiveAmount, route.toToken.priceUSD)\n : 0\n const canToggle = !!receiveAmount && !!route?.toToken.priceUSD\n\n const mainDisplay = showFiat\n ? t('format.currency', { value: fiatValue })\n : t('format.tokenAmount', { value: receiveAmount || '0' })\n const footerDisplay = showFiat\n ? t('format.tokenAmount', { value: receiveAmount || '0' })\n : t('format.currency', { value: fiatValue })\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: effect must run on value change\n useLayoutEffect(() => {\n if (amountRef.current) {\n fitInputText(maxInputFontSize, minInputFontSize, amountRef.current)\n }\n }, [mainDisplay])\n\n return (\n <ReceiveAmountCardLayout\n cardProps={props}\n title={isLimit ? t('header.buy') : t('header.receive')}\n headerEnd={\n isLimit && (isFetching || routes?.length) ? (\n <ProgressToNextUpdate\n updatedAt={dataUpdatedAt || Date.now()}\n timeToUpdate={refetchTime}\n isLoading={isFetching}\n onClick={() => refetch()}\n sx={{ padding: 0 }}\n />\n ) : undefined\n }\n amount={\n showSkeleton ? (\n <Skeleton variant=\"rounded\" height={amountHeight} sx={{ flex: 1 }} />\n ) : (\n <AmountDisplay\n ref={amountRef}\n sx={{ color: 'text.primary', opacity: receiveAmount ? 1 : 0.5 }}\n >\n {mainDisplay}\n </AmountDisplay>\n )\n }\n footerStart={\n showSkeleton ? (\n <Skeleton\n variant=\"text\"\n width={72}\n sx={{ fontSize: footerFontSize }}\n />\n ) : (\n <ToggleButton\n clickable={canToggle}\n onClick={canToggle ? () => toggleInputMode(formType) : undefined}\n aria-label={canToggle ? 'Toggle fiat value' : undefined}\n >\n <FooterText>{footerDisplay}</FooterText>\n {canToggle ? (\n <SwapVertIcon sx={{ fontSize: 14, color: 'text.secondary' }} />\n ) : null}\n </ToggleButton>\n )\n }\n footerEnd={\n showSkeleton ? (\n showPriceImpact ? (\n <Skeleton\n variant=\"text\"\n width={56}\n sx={{ fontSize: footerFontSize }}\n />\n ) : null\n ) : (\n <PriceImpactBadge priceImpact={priceImpact} />\n )\n }\n />\n )\n}\n"],"mappings":"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"}
@@ -0,0 +1,102 @@
1
+ import { useWidgetConfig } from "../../providers/WidgetProvider/WidgetProvider.js";
2
+ import { useFieldActions } from "../../stores/form/useFieldActions.js";
3
+ import { formatInputAmount, formatTokenPrice, priceToTokenAmount } from "../../utils/format.js";
4
+ import { FormKeyHelper } from "../../stores/form/types.js";
5
+ import { useFieldValues } from "../../stores/form/useFieldValues.js";
6
+ import { useToken } from "../../hooks/useToken.js";
7
+ import { useInputModeStore } from "../../stores/inputMode/useInputModeStore.js";
8
+ import { fitInputText } from "../../utils/input.js";
9
+ import { CardTitle } from "../Card/CardTitle.js";
10
+ import { useLinkedLimitFields } from "../../hooks/useLinkedLimitFields.js";
11
+ import { AmountCard, CardBodyRow, CardFooterRow, CardHeaderRow, LargeInput } from "./AmountInputCard.style.js";
12
+ import { TokenPillButton } from "../TokenPillButton/TokenPillButton.js";
13
+ import { BalanceDisplay } from "./BalanceDisplay.js";
14
+ import { FiatValueToggle } from "./FiatValueToggle.js";
15
+ import { PercentageChips } from "./PercentageChips.js";
16
+ import { useLayoutEffect, useRef, useState } from "react";
17
+ import { jsx, jsxs } from "react/jsx-runtime";
18
+ import { useTranslation } from "react-i18next";
19
+ //#region src/components/AmountInputCard/SendAmountCard.tsx
20
+ const SendAmountCard = (props) => {
21
+ const { t } = useTranslation();
22
+ const { disabledUI, mode, modeOptions } = useWidgetConfig();
23
+ const ref = useRef(null);
24
+ const isEditingRef = useRef(false);
25
+ const [formattedPriceInput, setFormattedPriceInput] = useState("");
26
+ const formType = "from";
27
+ const [chainId, tokenAddress, value] = useFieldValues(FormKeyHelper.getChainKey(formType), FormKeyHelper.getTokenKey(formType), FormKeyHelper.getAmountKey(formType));
28
+ const { token } = useToken(chainId, tokenAddress);
29
+ const { setFieldValue } = useFieldActions();
30
+ const { inputMode } = useInputModeStore();
31
+ const { setSendAmount } = useLinkedLimitFields();
32
+ const currentInputMode = inputMode[formType];
33
+ const disabled = disabledUI?.fromAmount;
34
+ const applyFromAmount = (value) => {
35
+ if (mode === "limit") setSendAmount(value);
36
+ else setFieldValue("fromAmount", value, {
37
+ isDirty: true,
38
+ isTouched: true
39
+ });
40
+ };
41
+ const title = mode === "limit" ? t("header.sell") : mode === "custom" && modeOptions?.custom?.type === "deposit" ? t("header.deposit") : t("header.send");
42
+ let displayValue;
43
+ if (isEditingRef.current) if (currentInputMode === "price") displayValue = formattedPriceInput;
44
+ else displayValue = value;
45
+ else if (currentInputMode === "price") displayValue = formatInputAmount(formatTokenPrice(value, token?.priceUSD).toFixed(2), 2);
46
+ else displayValue = value;
47
+ const handleChange = (event) => {
48
+ const { value: inputValue } = event.target;
49
+ isEditingRef.current = true;
50
+ if (currentInputMode === "price") {
51
+ const formattedValue = formatInputAmount(inputValue.replace("$", ""), 2, true);
52
+ const tokenValue = priceToTokenAmount(formattedValue, token?.priceUSD);
53
+ setFormattedPriceInput(formattedValue);
54
+ applyFromAmount(tokenValue);
55
+ } else {
56
+ const formattedValue = formatInputAmount(inputValue, token?.decimals, true);
57
+ applyFromAmount(formattedValue);
58
+ }
59
+ };
60
+ const handleBlur = (event) => {
61
+ const { value: inputValue } = event.target;
62
+ isEditingRef.current = false;
63
+ if (currentInputMode === "price") {
64
+ const formattedAmount = formatInputAmount(priceToTokenAmount(formatInputAmount(inputValue.replace("$", ""), 2), token?.priceUSD), token?.decimals);
65
+ applyFromAmount(formattedAmount);
66
+ } else {
67
+ const formattedValue = formatInputAmount(inputValue, token?.decimals);
68
+ applyFromAmount(formattedValue);
69
+ }
70
+ };
71
+ useLayoutEffect(() => {
72
+ if (ref.current) fitInputText(30, 20, ref.current);
73
+ }, [displayValue]);
74
+ return /* @__PURE__ */ jsxs(AmountCard, {
75
+ ...props,
76
+ formType,
77
+ children: [
78
+ /* @__PURE__ */ jsxs(CardHeaderRow, { children: [/* @__PURE__ */ jsx(CardTitle, {
79
+ sx: { padding: 0 },
80
+ children: title
81
+ }), /* @__PURE__ */ jsx(PercentageChips, { formType })] }),
82
+ /* @__PURE__ */ jsxs(CardBodyRow, { children: [/* @__PURE__ */ jsx(LargeInput, {
83
+ inputRef: ref,
84
+ size: "small",
85
+ autoComplete: "off",
86
+ placeholder: currentInputMode === "price" ? "$0" : "0",
87
+ inputProps: { inputMode: "decimal" },
88
+ onChange: handleChange,
89
+ onBlur: handleBlur,
90
+ value: currentInputMode === "price" ? displayValue ? `$${displayValue}` : "" : displayValue,
91
+ name: "fromAmount",
92
+ disabled,
93
+ required: true
94
+ }), /* @__PURE__ */ jsx(TokenPillButton, { formType })] }),
95
+ /* @__PURE__ */ jsxs(CardFooterRow, { children: [/* @__PURE__ */ jsx(FiatValueToggle, { formType }), /* @__PURE__ */ jsx(BalanceDisplay, { formType })] })
96
+ ]
97
+ });
98
+ };
99
+ //#endregion
100
+ export { SendAmountCard };
101
+
102
+ //# sourceMappingURL=SendAmountCard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SendAmountCard.js","names":[],"sources":["../../../../src/components/AmountInputCard/SendAmountCard.tsx"],"sourcesContent":["import type { CardProps } from '@mui/material'\nimport type { ChangeEvent, JSX } from 'react'\nimport { useLayoutEffect, useRef, useState } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { useLinkedLimitFields } from '../../hooks/useLinkedLimitFields.js'\nimport { useToken } from '../../hooks/useToken.js'\nimport { useWidgetConfig } from '../../providers/WidgetProvider/WidgetProvider.js'\nimport { FormKeyHelper } from '../../stores/form/types.js'\nimport { useFieldActions } from '../../stores/form/useFieldActions.js'\nimport { useFieldValues } from '../../stores/form/useFieldValues.js'\nimport { useInputModeStore } from '../../stores/inputMode/useInputModeStore.js'\nimport {\n formatInputAmount,\n formatTokenPrice,\n priceToTokenAmount,\n usdDecimals,\n} from '../../utils/format.js'\nimport { fitInputText } from '../../utils/input.js'\nimport { CardTitle } from '../Card/CardTitle.js'\nimport { TokenPillButton } from '../TokenPillButton/TokenPillButton.js'\nimport {\n AmountCard,\n CardBodyRow,\n CardFooterRow,\n CardHeaderRow,\n LargeInput,\n maxInputFontSize,\n minInputFontSize,\n} from './AmountInputCard.style.js'\nimport { BalanceDisplay } from './BalanceDisplay.js'\nimport { FiatValueToggle } from './FiatValueToggle.js'\nimport { PercentageChips } from './PercentageChips.js'\n\nexport const SendAmountCard: React.FC<CardProps & { mask?: boolean }> = (\n props\n): JSX.Element => {\n const { t } = useTranslation()\n const { disabledUI, mode, modeOptions } = useWidgetConfig()\n const ref = useRef<HTMLInputElement>(null)\n const isEditingRef = useRef(false)\n const [formattedPriceInput, setFormattedPriceInput] = useState('')\n\n const formType = 'from' as const\n\n const [chainId, tokenAddress, value] = useFieldValues(\n FormKeyHelper.getChainKey(formType),\n FormKeyHelper.getTokenKey(formType),\n FormKeyHelper.getAmountKey(formType)\n )\n\n const { token } = useToken(chainId, tokenAddress)\n const { setFieldValue } = useFieldActions()\n const { inputMode } = useInputModeStore()\n const { setSendAmount } = useLinkedLimitFields()\n const currentInputMode = inputMode[formType]\n const disabled = disabledUI?.fromAmount\n\n // In limit mode the send amount drives the linked receive amount; otherwise\n // it is a plain form-field write.\n const applyFromAmount = (value: string): void => {\n if (mode === 'limit') {\n setSendAmount(value)\n } else {\n setFieldValue('fromAmount', value, { isDirty: true, isTouched: true })\n }\n }\n\n const title =\n mode === 'limit'\n ? t('header.sell')\n : mode === 'custom' && modeOptions?.custom?.type === 'deposit'\n ? t('header.deposit')\n : t('header.send')\n\n let displayValue: string\n if (isEditingRef.current) {\n if (currentInputMode === 'price') {\n displayValue = formattedPriceInput\n } else {\n displayValue = value as string\n }\n } else {\n if (currentInputMode === 'price') {\n const priceValue = formatTokenPrice(value as string, token?.priceUSD)\n displayValue = formatInputAmount(\n priceValue.toFixed(usdDecimals),\n usdDecimals\n )\n } else {\n displayValue = value as string\n }\n }\n\n const handleChange = (\n event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>\n ): void => {\n const { value: inputValue } = event.target\n isEditingRef.current = true\n\n if (currentInputMode === 'price') {\n const cleanInputValue = inputValue.replace('$', '')\n const formattedValue = formatInputAmount(\n cleanInputValue,\n usdDecimals,\n true\n )\n const tokenValue = priceToTokenAmount(formattedValue, token?.priceUSD)\n setFormattedPriceInput(formattedValue)\n applyFromAmount(tokenValue)\n } else {\n const formattedValue = formatInputAmount(\n inputValue,\n token?.decimals,\n true\n )\n applyFromAmount(formattedValue)\n }\n }\n\n const handleBlur = (\n event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>\n ): void => {\n const { value: inputValue } = event.target\n isEditingRef.current = false\n\n if (currentInputMode === 'price') {\n const cleanInputValue = inputValue.replace('$', '')\n const formattedValue = formatInputAmount(cleanInputValue, usdDecimals)\n const tokenValue = priceToTokenAmount(formattedValue, token?.priceUSD)\n const formattedAmount = formatInputAmount(tokenValue, token?.decimals)\n applyFromAmount(formattedAmount)\n } else {\n const formattedValue = formatInputAmount(inputValue, token?.decimals)\n applyFromAmount(formattedValue)\n }\n }\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: effect must run on value change\n useLayoutEffect(() => {\n if (ref.current) {\n fitInputText(maxInputFontSize, minInputFontSize, ref.current)\n }\n }, [displayValue])\n\n return (\n <AmountCard {...props} formType={formType}>\n <CardHeaderRow>\n <CardTitle sx={{ padding: 0 }}>{title}</CardTitle>\n <PercentageChips formType={formType} />\n </CardHeaderRow>\n <CardBodyRow>\n <LargeInput\n inputRef={ref}\n size=\"small\"\n autoComplete=\"off\"\n placeholder={currentInputMode === 'price' ? '$0' : '0'}\n inputProps={{ inputMode: 'decimal' }}\n onChange={handleChange}\n onBlur={handleBlur}\n value={\n currentInputMode === 'price'\n ? displayValue\n ? `$${displayValue}`\n : ''\n : displayValue\n }\n name=\"fromAmount\"\n disabled={disabled}\n required\n />\n <TokenPillButton formType={formType} />\n </CardBodyRow>\n <CardFooterRow>\n <FiatValueToggle formType={formType} />\n <BalanceDisplay formType={formType} />\n </CardFooterRow>\n </AmountCard>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAiCA,MAAa,kBACX,UACgB;CAChB,MAAM,EAAE,MAAM,eAAe;CAC7B,MAAM,EAAE,YAAY,MAAM,gBAAgB,gBAAgB;CAC1D,MAAM,MAAM,OAAyB,IAAI;CACzC,MAAM,eAAe,OAAO,KAAK;CACjC,MAAM,CAAC,qBAAqB,0BAA0B,SAAS,EAAE;CAEjE,MAAM,WAAW;CAEjB,MAAM,CAAC,SAAS,cAAc,SAAS,eACrC,cAAc,YAAY,QAAQ,GAClC,cAAc,YAAY,QAAQ,GAClC,cAAc,aAAa,QAAQ,CACrC;CAEA,MAAM,EAAE,UAAU,SAAS,SAAS,YAAY;CAChD,MAAM,EAAE,kBAAkB,gBAAgB;CAC1C,MAAM,EAAE,cAAc,kBAAkB;CACxC,MAAM,EAAE,kBAAkB,qBAAqB;CAC/C,MAAM,mBAAmB,UAAU;CACnC,MAAM,WAAW,YAAY;CAI7B,MAAM,mBAAmB,UAAwB;EAC/C,IAAI,SAAS,SACX,cAAc,KAAK;OAEnB,cAAc,cAAc,OAAO;GAAE,SAAS;GAAM,WAAW;EAAK,CAAC;CAEzE;CAEA,MAAM,QACJ,SAAS,UACL,EAAE,aAAa,IACf,SAAS,YAAY,aAAa,QAAQ,SAAS,YACjD,EAAE,gBAAgB,IAClB,EAAE,aAAa;CAEvB,IAAI;CACJ,IAAI,aAAa,SACf,IAAI,qBAAqB,SACvB,eAAe;MAEf,eAAe;MAGjB,IAAI,qBAAqB,SAEvB,eAAe,kBADI,iBAAiB,OAAiB,OAAO,QAEjD,CAAC,CAAC,QAAA,CAAmB,GAAA,CAEhC;MAEA,eAAe;CAInB,MAAM,gBACJ,UACS;EACT,MAAM,EAAE,OAAO,eAAe,MAAM;EACpC,aAAa,UAAU;EAEvB,IAAI,qBAAqB,SAAS;GAEhC,MAAM,iBAAiB,kBADC,WAAW,QAAQ,KAAK,EAEhC,GAAA,GAEd,IACF;GACA,MAAM,aAAa,mBAAmB,gBAAgB,OAAO,QAAQ;GACrE,uBAAuB,cAAc;GACrC,gBAAgB,UAAU;EAC5B,OAAO;GACL,MAAM,iBAAiB,kBACrB,YACA,OAAO,UACP,IACF;GACA,gBAAgB,cAAc;EAChC;CACF;CAEA,MAAM,cACJ,UACS;EACT,MAAM,EAAE,OAAO,eAAe,MAAM;EACpC,aAAa,UAAU;EAEvB,IAAI,qBAAqB,SAAS;GAIhC,MAAM,kBAAkB,kBADL,mBADI,kBADC,WAAW,QAAQ,KAAK,EACO,GAAA,CACJ,GAAG,OAAO,QACV,GAAG,OAAO,QAAQ;GACrE,gBAAgB,eAAe;EACjC,OAAO;GACL,MAAM,iBAAiB,kBAAkB,YAAY,OAAO,QAAQ;GACpE,gBAAgB,cAAc;EAChC;CACF;CAGA,sBAAsB;EACpB,IAAI,IAAI,SACN,aAAA,IAAA,IAAiD,IAAI,OAAO;CAEhE,GAAG,CAAC,YAAY,CAAC;CAEjB,OACE,qBAAC,YAAD;EAAY,GAAI;EAAiB;YAAjC;GACE,qBAAC,eAAD,EAAA,UAAA,CACE,oBAAC,WAAD;IAAW,IAAI,EAAE,SAAS,EAAE;cAAI;GAAiB,CAAA,GACjD,oBAAC,iBAAD,EAA2B,SAAW,CAAA,CACzB,EAAA,CAAA;GACf,qBAAC,aAAD,EAAA,UAAA,CACE,oBAAC,YAAD;IACE,UAAU;IACV,MAAK;IACL,cAAa;IACb,aAAa,qBAAqB,UAAU,OAAO;IACnD,YAAY,EAAE,WAAW,UAAU;IACnC,UAAU;IACV,QAAQ;IACR,OACE,qBAAqB,UACjB,eACE,IAAI,iBACJ,KACF;IAEN,MAAK;IACK;IACV,UAAA;GACD,CAAA,GACD,oBAAC,iBAAD,EAA2B,SAAW,CAAA,CAC3B,EAAA,CAAA;GACb,qBAAC,eAAD,EAAA,UAAA,CACE,oBAAC,iBAAD,EAA2B,SAAW,CAAA,GACtC,oBAAC,gBAAD,EAA0B,SAAW,CAAA,CACxB,EAAA,CAAA;EACL;;AAEhB"}
@@ -4,16 +4,25 @@ import { ChainBadgeContent } from "./ChainBadgeContent.js";
4
4
  import { Badge, Skeleton } from "@mui/material";
5
5
  import { jsx } from "react/jsx-runtime";
6
6
  //#region src/components/Avatar/Avatar.tsx
7
- const AvatarBadgedDefault = ({ sx, chain }) => {
7
+ const AvatarBadgedDefault = ({ sx, chain, avatarSize, badgeSize }) => {
8
8
  return /* @__PURE__ */ jsx(Badge, {
9
9
  overlap: "circular",
10
10
  anchorOrigin: {
11
11
  vertical: "bottom",
12
12
  horizontal: "right"
13
13
  },
14
- badgeContent: /* @__PURE__ */ jsx(ChainBadgeContent, { chain }),
14
+ badgeContent: /* @__PURE__ */ jsx(ChainBadgeContent, {
15
+ chain,
16
+ size: badgeSize
17
+ }),
15
18
  sx,
16
- children: /* @__PURE__ */ jsx(AvatarDefault, {})
19
+ children: /* @__PURE__ */ jsx(AvatarDefault, {
20
+ badgeSize,
21
+ sx: avatarSize ? {
22
+ width: avatarSize,
23
+ height: avatarSize
24
+ } : void 0
25
+ })
17
26
  });
18
27
  };
19
28
  const AvatarBadgedSkeleton = ({ sx, avatarSize = 40, badgeSize = 16 }) => {
@@ -1 +1 @@
1
- {"version":3,"file":"Avatar.js","names":[],"sources":["../../../../src/components/Avatar/Avatar.tsx"],"sourcesContent":["import type { ExtendedChain } from '@lifi/sdk'\nimport type { SxProps, Theme } from '@mui/material'\nimport { Badge, Skeleton } from '@mui/material'\nimport { AvatarDefault, AvatarSkeletonMaskedContainer } from './Avatar.style.js'\nimport { ChainBadgeContent } from './ChainBadgeContent.js'\nimport { SmallAvatarSkeleton } from './SmallAvatar.js'\n\nexport const AvatarBadgedDefault: React.FC<{\n sx?: SxProps<Theme>\n chain?: ExtendedChain\n}> = ({ sx, chain }) => {\n return (\n <Badge\n overlap=\"circular\"\n anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}\n badgeContent={<ChainBadgeContent chain={chain} />}\n sx={sx}\n >\n <AvatarDefault />\n </Badge>\n )\n}\n\nexport const AvatarBadgedSkeleton: React.FC<{\n sx?: SxProps<Theme>\n avatarSize?: number\n badgeSize?: number\n}> = ({ sx, avatarSize = 40, badgeSize = 16 }) => {\n return (\n <Badge\n overlap=\"circular\"\n anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}\n badgeContent={<SmallAvatarSkeleton size={badgeSize} />}\n sx={sx}\n >\n <AvatarSkeleton avatarSize={avatarSize} badgeSize={badgeSize} />\n </Badge>\n )\n}\n\nconst AvatarSkeleton: React.FC<{\n avatarSize: number\n badgeSize: number\n}> = ({ avatarSize, badgeSize }) => {\n return (\n <AvatarSkeletonMaskedContainer badgeSize={badgeSize}>\n <Skeleton width={avatarSize} height={avatarSize} variant=\"circular\" />\n </AvatarSkeletonMaskedContainer>\n )\n}\n"],"mappings":";;;;;;AAOA,MAAa,uBAGP,EAAE,IAAI,YAAY;CACtB,OACE,oBAAC,OAAD;EACE,SAAQ;EACR,cAAc;GAAE,UAAU;GAAU,YAAY;EAAQ;EACxD,cAAc,oBAAC,mBAAD,EAA0B,MAAQ,CAAA;EAC5C;YAEJ,oBAAC,eAAD,CAAgB,CAAA;CACX,CAAA;AAEX;AAEA,MAAa,wBAIP,EAAE,IAAI,aAAa,IAAI,YAAY,SAAS;CAChD,OACE,oBAAC,OAAD;EACE,SAAQ;EACR,cAAc;GAAE,UAAU;GAAU,YAAY;EAAQ;EACxD,cAAc,oBAAC,qBAAD,EAAqB,MAAM,UAAY,CAAA;EACjD;YAEJ,oBAAC,gBAAD;GAA4B;GAAuB;EAAY,CAAA;CAC1D,CAAA;AAEX;AAEA,MAAM,kBAGA,EAAE,YAAY,gBAAgB;CAClC,OACE,oBAAC,+BAAD;EAA0C;YACxC,oBAAC,UAAD;GAAU,OAAO;GAAY,QAAQ;GAAY,SAAQ;EAAY,CAAA;CACxC,CAAA;AAEnC"}
1
+ {"version":3,"file":"Avatar.js","names":[],"sources":["../../../../src/components/Avatar/Avatar.tsx"],"sourcesContent":["import type { ExtendedChain } from '@lifi/sdk'\nimport type { SxProps, Theme } from '@mui/material'\nimport { Badge, Skeleton } from '@mui/material'\nimport { AvatarDefault, AvatarSkeletonMaskedContainer } from './Avatar.style.js'\nimport { ChainBadgeContent } from './ChainBadgeContent.js'\nimport { SmallAvatarSkeleton } from './SmallAvatar.js'\n\nexport const AvatarBadgedDefault: React.FC<{\n sx?: SxProps<Theme>\n chain?: ExtendedChain\n avatarSize?: number\n badgeSize?: number\n}> = ({ sx, chain, avatarSize, badgeSize }) => {\n return (\n <Badge\n overlap=\"circular\"\n anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}\n badgeContent={<ChainBadgeContent chain={chain} size={badgeSize} />}\n sx={sx}\n >\n <AvatarDefault\n badgeSize={badgeSize}\n sx={avatarSize ? { width: avatarSize, height: avatarSize } : undefined}\n />\n </Badge>\n )\n}\n\nexport const AvatarBadgedSkeleton: React.FC<{\n sx?: SxProps<Theme>\n avatarSize?: number\n badgeSize?: number\n}> = ({ sx, avatarSize = 40, badgeSize = 16 }) => {\n return (\n <Badge\n overlap=\"circular\"\n anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}\n badgeContent={<SmallAvatarSkeleton size={badgeSize} />}\n sx={sx}\n >\n <AvatarSkeleton avatarSize={avatarSize} badgeSize={badgeSize} />\n </Badge>\n )\n}\n\nconst AvatarSkeleton: React.FC<{\n avatarSize: number\n badgeSize: number\n}> = ({ avatarSize, badgeSize }) => {\n return (\n <AvatarSkeletonMaskedContainer badgeSize={badgeSize}>\n <Skeleton width={avatarSize} height={avatarSize} variant=\"circular\" />\n </AvatarSkeletonMaskedContainer>\n )\n}\n"],"mappings":";;;;;;AAOA,MAAa,uBAKP,EAAE,IAAI,OAAO,YAAY,gBAAgB;CAC7C,OACE,oBAAC,OAAD;EACE,SAAQ;EACR,cAAc;GAAE,UAAU;GAAU,YAAY;EAAQ;EACxD,cAAc,oBAAC,mBAAD;GAA0B;GAAO,MAAM;EAAY,CAAA;EAC7D;YAEJ,oBAAC,eAAD;GACa;GACX,IAAI,aAAa;IAAE,OAAO;IAAY,QAAQ;GAAW,IAAI,KAAA;EAC9D,CAAA;CACI,CAAA;AAEX;AAEA,MAAa,wBAIP,EAAE,IAAI,aAAa,IAAI,YAAY,SAAS;CAChD,OACE,oBAAC,OAAD;EACE,SAAQ;EACR,cAAc;GAAE,UAAU;GAAU,YAAY;EAAQ;EACxD,cAAc,oBAAC,qBAAD,EAAqB,MAAM,UAAY,CAAA;EACjD;YAEJ,oBAAC,gBAAD;GAA4B;GAAuB;EAAY,CAAA;CAC1D,CAAA;AAEX;AAEA,MAAM,kBAGA,EAAE,YAAY,gBAAgB;CAClC,OACE,oBAAC,+BAAD;EAA0C;YACxC,oBAAC,UAAD;GAAU,OAAO;GAAY,QAAQ;GAAY,SAAQ;EAAY,CAAA;CACxC,CAAA;AAEnC"}
@@ -34,7 +34,8 @@ const VirtualizedChainList = ({ formType, chains, hasSearchQuery, onSelect, sele
34
34
  const showAllNetworksOption = showAllNetworks && !hasSearchQuery;
35
35
  const getItemKey = useCallback((index) => {
36
36
  if (showAllNetworksOption && index === 0) return "all-chains";
37
- return `${sortedChains[index - (showAllNetworksOption ? 1 : 0)].id}-${index}`;
37
+ const chainIndex = index - (showAllNetworksOption ? 1 : 0);
38
+ return `${sortedChains[chainIndex].id}-${index}`;
38
39
  }, [sortedChains, showAllNetworksOption]);
39
40
  const onChainSelect = useCallback((chain) => {
40
41
  setIsAllNetworks(false, formType);
@@ -1 +1 @@
1
- {"version":3,"file":"VirtualizedChainList.js","names":[],"sources":["../../../../src/components/Chains/VirtualizedChainList.tsx"],"sourcesContent":["import type { ExtendedChain } from '@lifi/sdk'\nimport { useVirtualizer } from '@tanstack/react-virtual'\nimport type { JSX, RefObject } from 'react'\nimport { useCallback, useEffect, useMemo, useRef } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { useChainOrderStore } from '../../stores/chains/ChainOrderStore.js'\nimport { FormKeyHelper, type FormType } from '../../stores/form/types.js'\nimport { useFieldActions } from '../../stores/form/useFieldActions.js'\nimport { AllChainsAvatar } from './AllChainsAvatar.js'\nimport {\n List,\n ListItem,\n ListItemAvatar,\n ListItemButton,\n ListItemText,\n} from './ChainList.style.js'\nimport { ChainListItem } from './ChainListItem.js'\n\ninterface VirtualizedChainListProps {\n scrollElementRef: RefObject<HTMLDivElement | null>\n formType: FormType\n chains: ExtendedChain[]\n onSelect: (chain: ExtendedChain) => void\n selectedChainId?: number\n itemsSize: 'small' | 'medium'\n hasSearchQuery: boolean\n withPinnedChains: boolean\n}\n\nexport const VirtualizedChainList = ({\n formType,\n chains,\n hasSearchQuery,\n onSelect,\n selectedChainId,\n itemsSize,\n scrollElementRef,\n withPinnedChains,\n}: VirtualizedChainListProps): JSX.Element => {\n const { t } = useTranslation()\n const selectedChainIdRef = useRef(selectedChainId) // Store the initial selected chain ID to scroll to it once chains are loaded\n const hasScrolledRef = useRef(false)\n const [\n pinnedChains,\n setPinnedChain,\n isAllNetworks,\n setIsAllNetworks,\n showAllNetworks,\n ] = useChainOrderStore((state) => [\n state.pinnedChains,\n state.setPinnedChain,\n state[`${formType}IsAllNetworks`],\n state.setIsAllNetworks,\n state[`${formType}ShowAllNetworks`],\n ])\n const { setFieldValue } = useFieldActions()\n const onPin = useCallback(\n (chainId: number) => {\n setPinnedChain(chainId)\n },\n [setPinnedChain]\n )\n\n const sortedChains = useMemo(() => {\n if (!pinnedChains.length) {\n return chains\n }\n // Pinning logic: move pinned chains to the top of the list\n const pinned = pinnedChains\n .map((id) => chains.find((c) => c.id === id))\n .filter(Boolean) as ExtendedChain[]\n const pinnedIds = new Set(pinned.map((c) => c.id))\n const rest = chains.filter((c) => !pinnedIds.has(c.id))\n return [...pinned, ...rest]\n }, [chains, pinnedChains])\n\n const showAllNetworksOption = showAllNetworks && !hasSearchQuery\n\n const getItemKey = useCallback(\n (index: number) => {\n if (showAllNetworksOption && index === 0) {\n return 'all-chains'\n }\n const chainIndex = index - (showAllNetworksOption ? 1 : 0)\n return `${sortedChains[chainIndex].id}-${index}`\n },\n [sortedChains, showAllNetworksOption]\n )\n\n const onChainSelect = useCallback(\n (chain: ExtendedChain) => {\n setIsAllNetworks(false, formType)\n onSelect(chain)\n },\n [onSelect, setIsAllNetworks, formType]\n )\n\n const { getVirtualItems, getTotalSize, measure, range, getOffsetForIndex } =\n useVirtualizer({\n count: sortedChains.length + (showAllNetworksOption ? 1 : 0), // +1 for the all networks item\n overscan: 3,\n paddingEnd: 0,\n getScrollElement: () => scrollElementRef.current,\n estimateSize: () => {\n return itemsSize === 'small' ? 48 : 60\n },\n getItemKey,\n })\n\n // Using mountOnEnter of the ExpansionTransition component\n // leads to a short delay for setting up scrollElementRef,\n // which in turn leads to getVirtualItems() returning an empty array.\n // Workaround: Re-measure when scroll element becomes available\n useEffect(() => {\n if (scrollElementRef.current) {\n measure()\n }\n }, [measure, scrollElementRef.current])\n\n const scrollToIndex = useCallback(\n (index: number) => {\n requestAnimationFrame(() => {\n const offsetInfo = getOffsetForIndex(index, 'center')\n if (!scrollElementRef.current || !offsetInfo) {\n return\n }\n scrollElementRef.current.scrollTo({\n top: offsetInfo[0],\n left: 0,\n behavior: 'smooth',\n })\n })\n },\n [getOffsetForIndex, scrollElementRef.current]\n )\n\n useEffect(() => {\n // Mark as scrolled if \"All Networks\" is initially selected\n if (isAllNetworks) {\n hasScrolledRef.current = true\n return\n }\n\n // Only scroll if sortedChains is not empty and we haven't scrolled yet\n if (!hasScrolledRef.current && sortedChains.length > 0 && range) {\n const selectedChainIndex = sortedChains.findIndex(\n (chain) => chain.id === selectedChainIdRef.current\n )\n if (selectedChainIndex !== -1) {\n // Only scroll if the selected chain is not in the visible range\n // +1 and -1 to account for partially visible items\n if (\n range.startIndex + 1 > selectedChainIndex ||\n range.endIndex - 1 < selectedChainIndex\n ) {\n scrollToIndex(selectedChainIndex)\n }\n }\n hasScrolledRef.current = true // Mark as scrolled (when needed)\n }\n }, [sortedChains, scrollToIndex, range, isAllNetworks])\n\n const selectAllNetworks = useCallback(() => {\n setIsAllNetworks(true, formType)\n // Reset the chain and token fields when selecting all networks\n setFieldValue(FormKeyHelper.getChainKey(formType), '', { isTouched: true })\n setFieldValue(FormKeyHelper.getTokenKey(formType), '', { isTouched: true })\n }, [setIsAllNetworks, formType, setFieldValue])\n\n return (\n <List\n className=\"long-list\"\n style={{ height: getTotalSize() }}\n disablePadding\n >\n {getVirtualItems().map((item) => {\n if (showAllNetworksOption && item.index === 0) {\n return (\n <ListItem\n key={item.key}\n style={{\n height: `${itemsSize}px`,\n transform: `translateY(${item.start}px)`,\n padding: 0,\n }}\n >\n <ListItemButton\n onClick={selectAllNetworks}\n selected={isAllNetworks}\n size={itemsSize}\n >\n <ListItemAvatar size={itemsSize}>\n <AllChainsAvatar chains={chains} size={itemsSize} />\n </ListItemAvatar>\n <ListItemText\n primary={t('main.allNetworks')}\n size={itemsSize}\n />\n </ListItemButton>\n </ListItem>\n )\n }\n\n const chain = sortedChains[item.index - (showAllNetworksOption ? 1 : 0)]\n return (\n <ChainListItem\n key={item.key}\n chain={chain}\n onSelect={onChainSelect}\n selected={!isAllNetworks && chain.id === selectedChainId}\n itemsSize={itemsSize}\n size={item.size}\n start={item.start}\n withPin={withPinnedChains}\n isPinned={pinnedChains.includes(chain.id)}\n onPin={onPin}\n />\n )\n })}\n </List>\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"VirtualizedChainList.js","names":[],"sources":["../../../../src/components/Chains/VirtualizedChainList.tsx"],"sourcesContent":["import type { ExtendedChain } from '@lifi/sdk'\nimport { useVirtualizer } from '@tanstack/react-virtual'\nimport type { JSX, RefObject } from 'react'\nimport { useCallback, useEffect, useMemo, useRef } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { useChainOrderStore } from '../../stores/chains/ChainOrderStore.js'\nimport { FormKeyHelper, type FormType } from '../../stores/form/types.js'\nimport { useFieldActions } from '../../stores/form/useFieldActions.js'\nimport { AllChainsAvatar } from './AllChainsAvatar.js'\nimport {\n List,\n ListItem,\n ListItemAvatar,\n ListItemButton,\n ListItemText,\n} from './ChainList.style.js'\nimport { ChainListItem } from './ChainListItem.js'\n\ninterface VirtualizedChainListProps {\n scrollElementRef: RefObject<HTMLDivElement | null>\n formType: FormType\n chains: ExtendedChain[]\n onSelect: (chain: ExtendedChain) => void\n selectedChainId?: number\n itemsSize: 'small' | 'medium'\n hasSearchQuery: boolean\n withPinnedChains: boolean\n}\n\nexport const VirtualizedChainList = ({\n formType,\n chains,\n hasSearchQuery,\n onSelect,\n selectedChainId,\n itemsSize,\n scrollElementRef,\n withPinnedChains,\n}: VirtualizedChainListProps): JSX.Element => {\n const { t } = useTranslation()\n const selectedChainIdRef = useRef(selectedChainId) // Store the initial selected chain ID to scroll to it once chains are loaded\n const hasScrolledRef = useRef(false)\n const [\n pinnedChains,\n setPinnedChain,\n isAllNetworks,\n setIsAllNetworks,\n showAllNetworks,\n ] = useChainOrderStore((state) => [\n state.pinnedChains,\n state.setPinnedChain,\n state[`${formType}IsAllNetworks`],\n state.setIsAllNetworks,\n state[`${formType}ShowAllNetworks`],\n ])\n const { setFieldValue } = useFieldActions()\n const onPin = useCallback(\n (chainId: number) => {\n setPinnedChain(chainId)\n },\n [setPinnedChain]\n )\n\n const sortedChains = useMemo(() => {\n if (!pinnedChains.length) {\n return chains\n }\n // Pinning logic: move pinned chains to the top of the list\n const pinned = pinnedChains\n .map((id) => chains.find((c) => c.id === id))\n .filter(Boolean) as ExtendedChain[]\n const pinnedIds = new Set(pinned.map((c) => c.id))\n const rest = chains.filter((c) => !pinnedIds.has(c.id))\n return [...pinned, ...rest]\n }, [chains, pinnedChains])\n\n const showAllNetworksOption = showAllNetworks && !hasSearchQuery\n\n const getItemKey = useCallback(\n (index: number) => {\n if (showAllNetworksOption && index === 0) {\n return 'all-chains'\n }\n const chainIndex = index - (showAllNetworksOption ? 1 : 0)\n return `${sortedChains[chainIndex].id}-${index}`\n },\n [sortedChains, showAllNetworksOption]\n )\n\n const onChainSelect = useCallback(\n (chain: ExtendedChain) => {\n setIsAllNetworks(false, formType)\n onSelect(chain)\n },\n [onSelect, setIsAllNetworks, formType]\n )\n\n const { getVirtualItems, getTotalSize, measure, range, getOffsetForIndex } =\n useVirtualizer({\n count: sortedChains.length + (showAllNetworksOption ? 1 : 0), // +1 for the all networks item\n overscan: 3,\n paddingEnd: 0,\n getScrollElement: () => scrollElementRef.current,\n estimateSize: () => {\n return itemsSize === 'small' ? 48 : 60\n },\n getItemKey,\n })\n\n // Using mountOnEnter of the ExpansionTransition component\n // leads to a short delay for setting up scrollElementRef,\n // which in turn leads to getVirtualItems() returning an empty array.\n // Workaround: Re-measure when scroll element becomes available\n useEffect(() => {\n if (scrollElementRef.current) {\n measure()\n }\n }, [measure, scrollElementRef.current])\n\n const scrollToIndex = useCallback(\n (index: number) => {\n requestAnimationFrame(() => {\n const offsetInfo = getOffsetForIndex(index, 'center')\n if (!scrollElementRef.current || !offsetInfo) {\n return\n }\n scrollElementRef.current.scrollTo({\n top: offsetInfo[0],\n left: 0,\n behavior: 'smooth',\n })\n })\n },\n [getOffsetForIndex, scrollElementRef.current]\n )\n\n useEffect(() => {\n // Mark as scrolled if \"All Networks\" is initially selected\n if (isAllNetworks) {\n hasScrolledRef.current = true\n return\n }\n\n // Only scroll if sortedChains is not empty and we haven't scrolled yet\n if (!hasScrolledRef.current && sortedChains.length > 0 && range) {\n const selectedChainIndex = sortedChains.findIndex(\n (chain) => chain.id === selectedChainIdRef.current\n )\n if (selectedChainIndex !== -1) {\n // Only scroll if the selected chain is not in the visible range\n // +1 and -1 to account for partially visible items\n if (\n range.startIndex + 1 > selectedChainIndex ||\n range.endIndex - 1 < selectedChainIndex\n ) {\n scrollToIndex(selectedChainIndex)\n }\n }\n hasScrolledRef.current = true // Mark as scrolled (when needed)\n }\n }, [sortedChains, scrollToIndex, range, isAllNetworks])\n\n const selectAllNetworks = useCallback(() => {\n setIsAllNetworks(true, formType)\n // Reset the chain and token fields when selecting all networks\n setFieldValue(FormKeyHelper.getChainKey(formType), '', { isTouched: true })\n setFieldValue(FormKeyHelper.getTokenKey(formType), '', { isTouched: true })\n }, [setIsAllNetworks, formType, setFieldValue])\n\n return (\n <List\n className=\"long-list\"\n style={{ height: getTotalSize() }}\n disablePadding\n >\n {getVirtualItems().map((item) => {\n if (showAllNetworksOption && item.index === 0) {\n return (\n <ListItem\n key={item.key}\n style={{\n height: `${itemsSize}px`,\n transform: `translateY(${item.start}px)`,\n padding: 0,\n }}\n >\n <ListItemButton\n onClick={selectAllNetworks}\n selected={isAllNetworks}\n size={itemsSize}\n >\n <ListItemAvatar size={itemsSize}>\n <AllChainsAvatar chains={chains} size={itemsSize} />\n </ListItemAvatar>\n <ListItemText\n primary={t('main.allNetworks')}\n size={itemsSize}\n />\n </ListItemButton>\n </ListItem>\n )\n }\n\n const chain = sortedChains[item.index - (showAllNetworksOption ? 1 : 0)]\n return (\n <ChainListItem\n key={item.key}\n chain={chain}\n onSelect={onChainSelect}\n selected={!isAllNetworks && chain.id === selectedChainId}\n itemsSize={itemsSize}\n size={item.size}\n start={item.start}\n withPin={withPinnedChains}\n isPinned={pinnedChains.includes(chain.id)}\n onPin={onPin}\n />\n )\n })}\n </List>\n )\n}\n"],"mappings":";;;;;;;;;;;AA6BA,MAAa,wBAAwB,EACnC,UACA,QACA,gBACA,UACA,iBACA,WACA,kBACA,uBAC4C;CAC5C,MAAM,EAAE,MAAM,eAAe;CAC7B,MAAM,qBAAqB,OAAO,eAAe;CACjD,MAAM,iBAAiB,OAAO,KAAK;CACnC,MAAM,CACJ,cACA,gBACA,eACA,kBACA,mBACE,oBAAoB,UAAU;EAChC,MAAM;EACN,MAAM;EACN,MAAM,GAAG,SAAS;EAClB,MAAM;EACN,MAAM,GAAG,SAAS;CACpB,CAAC;CACD,MAAM,EAAE,kBAAkB,gBAAgB;CAC1C,MAAM,QAAQ,aACX,YAAoB;EACnB,eAAe,OAAO;CACxB,GACA,CAAC,cAAc,CACjB;CAEA,MAAM,eAAe,cAAc;EACjC,IAAI,CAAC,aAAa,QAChB,OAAO;EAGT,MAAM,SAAS,aACZ,KAAK,OAAO,OAAO,MAAM,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,CAC5C,OAAO,OAAO;EACjB,MAAM,YAAY,IAAI,IAAI,OAAO,KAAK,MAAM,EAAE,EAAE,CAAC;EACjD,MAAM,OAAO,OAAO,QAAQ,MAAM,CAAC,UAAU,IAAI,EAAE,EAAE,CAAC;EACtD,OAAO,CAAC,GAAG,QAAQ,GAAG,IAAI;CAC5B,GAAG,CAAC,QAAQ,YAAY,CAAC;CAEzB,MAAM,wBAAwB,mBAAmB,CAAC;CAElD,MAAM,aAAa,aAChB,UAAkB;EACjB,IAAI,yBAAyB,UAAU,GACrC,OAAO;EAET,MAAM,aAAa,SAAS,wBAAwB,IAAI;EACxD,OAAO,GAAG,aAAa,WAAW,CAAC,GAAG,GAAG;CAC3C,GACA,CAAC,cAAc,qBAAqB,CACtC;CAEA,MAAM,gBAAgB,aACnB,UAAyB;EACxB,iBAAiB,OAAO,QAAQ;EAChC,SAAS,KAAK;CAChB,GACA;EAAC;EAAU;EAAkB;CAAQ,CACvC;CAEA,MAAM,EAAE,iBAAiB,cAAc,SAAS,OAAO,sBACrD,eAAe;EACb,OAAO,aAAa,UAAU,wBAAwB,IAAI;EAC1D,UAAU;EACV,YAAY;EACZ,wBAAwB,iBAAiB;EACzC,oBAAoB;GAClB,OAAO,cAAc,UAAU,KAAK;EACtC;EACA;CACF,CAAC;CAMH,gBAAgB;EACd,IAAI,iBAAiB,SACnB,QAAQ;CAEZ,GAAG,CAAC,SAAS,iBAAiB,OAAO,CAAC;CAEtC,MAAM,gBAAgB,aACnB,UAAkB;EACjB,4BAA4B;GAC1B,MAAM,aAAa,kBAAkB,OAAO,QAAQ;GACpD,IAAI,CAAC,iBAAiB,WAAW,CAAC,YAChC;GAEF,iBAAiB,QAAQ,SAAS;IAChC,KAAK,WAAW;IAChB,MAAM;IACN,UAAU;GACZ,CAAC;EACH,CAAC;CACH,GACA,CAAC,mBAAmB,iBAAiB,OAAO,CAC9C;CAEA,gBAAgB;EAEd,IAAI,eAAe;GACjB,eAAe,UAAU;GACzB;EACF;EAGA,IAAI,CAAC,eAAe,WAAW,aAAa,SAAS,KAAK,OAAO;GAC/D,MAAM,qBAAqB,aAAa,WACrC,UAAU,MAAM,OAAO,mBAAmB,OAC7C;GACA,IAAI,uBAAuB;QAIvB,MAAM,aAAa,IAAI,sBACvB,MAAM,WAAW,IAAI,oBAErB,cAAc,kBAAkB;GAAA;GAGpC,eAAe,UAAU;EAC3B;CACF,GAAG;EAAC;EAAc;EAAe;EAAO;CAAa,CAAC;CAEtD,MAAM,oBAAoB,kBAAkB;EAC1C,iBAAiB,MAAM,QAAQ;EAE/B,cAAc,cAAc,YAAY,QAAQ,GAAG,IAAI,EAAE,WAAW,KAAK,CAAC;EAC1E,cAAc,cAAc,YAAY,QAAQ,GAAG,IAAI,EAAE,WAAW,KAAK,CAAC;CAC5E,GAAG;EAAC;EAAkB;EAAU;CAAa,CAAC;CAE9C,OACE,oBAAC,MAAD;EACE,WAAU;EACV,OAAO,EAAE,QAAQ,aAAa,EAAE;EAChC,gBAAA;YAEC,gBAAgB,CAAC,CAAC,KAAK,SAAS;GAC/B,IAAI,yBAAyB,KAAK,UAAU,GAC1C,OACE,oBAAC,UAAD;IAEE,OAAO;KACL,QAAQ,GAAG,UAAU;KACrB,WAAW,cAAc,KAAK,MAAM;KACpC,SAAS;IACX;cAEA,qBAAC,gBAAD;KACE,SAAS;KACT,UAAU;KACV,MAAM;eAHR,CAKE,oBAAC,gBAAD;MAAgB,MAAM;gBACpB,oBAAC,iBAAD;OAAyB;OAAQ,MAAM;MAAY,CAAA;KACrC,CAAA,GAChB,oBAAC,cAAD;MACE,SAAS,EAAE,kBAAkB;MAC7B,MAAM;KACP,CAAA,CACa;;GACR,GApBH,KAAK,GAoBF;GAId,MAAM,QAAQ,aAAa,KAAK,SAAS,wBAAwB,IAAI;GACrE,OACE,oBAAC,eAAD;IAES;IACP,UAAU;IACV,UAAU,CAAC,iBAAiB,MAAM,OAAO;IAC9B;IACX,MAAM,KAAK;IACX,OAAO,KAAK;IACZ,SAAS;IACT,UAAU,aAAa,SAAS,MAAM,EAAE;IACjC;GACR,GAVM,KAAK,GAUX;EAEL,CAAC;CACG,CAAA;AAEV"}
@@ -1,6 +1,9 @@
1
+ import { useWidgetEvents } from "../../hooks/useWidgetEvents.js";
2
+ import "../../types/events.js";
1
3
  import { useNavigationTabsStore } from "../../stores/navigationTabs/useNavigationTabsStore.js";
2
4
  import { useNavigationTabLabel } from "../../stores/navigationTabs/useNavigationTabLabel.js";
3
5
  import { HeaderTabs } from "./HeaderTabs.js";
6
+ import { useEffect, useRef } from "react";
4
7
  import { jsx } from "react/jsx-runtime";
5
8
  //#region src/components/Header/HeaderNavigationTabs.tsx
6
9
  const HeaderNavigationTabs = () => {
@@ -10,6 +13,16 @@ const HeaderNavigationTabs = () => {
10
13
  store.setActiveTab
11
14
  ]);
12
15
  const getLabel = useNavigationTabLabel();
16
+ const emitter = useWidgetEvents();
17
+ const previousTabRef = useRef(void 0);
18
+ useEffect(() => {
19
+ if (!activeTab || previousTabRef.current === activeTab) return;
20
+ emitter.emit("navigationTabChanged", {
21
+ tab: activeTab,
22
+ previousTab: previousTabRef.current
23
+ });
24
+ previousTabRef.current = activeTab;
25
+ }, [emitter, activeTab]);
13
26
  if (!tabs.length || !activeTab) return null;
14
27
  return /* @__PURE__ */ jsx(HeaderTabs, {
15
28
  tabs: tabs.map((key) => ({
@@ -17,7 +30,7 @@ const HeaderNavigationTabs = () => {
17
30
  label: getLabel(key)
18
31
  })),
19
32
  value: activeTab,
20
- onChange: (key) => setActiveTab(key)
33
+ onChange: setActiveTab
21
34
  });
22
35
  };
23
36
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"HeaderNavigationTabs.js","names":[],"sources":["../../../../src/components/Header/HeaderNavigationTabs.tsx"],"sourcesContent":["import type { JSX } from 'react'\nimport { useNavigationTabLabel } from '../../stores/navigationTabs/useNavigationTabLabel.js'\nimport { useNavigationTabsStore } from '../../stores/navigationTabs/useNavigationTabsStore.js'\nimport { HeaderTabs } from './HeaderTabs.js'\n\nexport const HeaderNavigationTabs = (): JSX.Element | null => {\n const [tabs, activeTab, setActiveTab] = useNavigationTabsStore((store) => [\n store.tabs,\n store.activeTab,\n store.setActiveTab,\n ])\n const getLabel = useNavigationTabLabel()\n\n if (!tabs.length || !activeTab) {\n return null\n }\n\n return (\n <HeaderTabs\n tabs={tabs.map((key) => ({ key, label: getLabel(key) }))}\n value={activeTab}\n onChange={(key) => setActiveTab(key)}\n />\n )\n}\n"],"mappings":";;;;;AAKA,MAAa,6BAAiD;CAC5D,MAAM,CAAC,MAAM,WAAW,gBAAgB,wBAAwB,UAAU;EACxE,MAAM;EACN,MAAM;EACN,MAAM;CACR,CAAC;CACD,MAAM,WAAW,sBAAsB;CAEvC,IAAI,CAAC,KAAK,UAAU,CAAC,WACnB,OAAO;CAGT,OACE,oBAAC,YAAD;EACE,MAAM,KAAK,KAAK,SAAS;GAAE;GAAK,OAAO,SAAS,GAAG;EAAE,EAAE;EACvD,OAAO;EACP,WAAW,QAAQ,aAAa,GAAG;CACpC,CAAA;AAEL"}
1
+ {"version":3,"file":"HeaderNavigationTabs.js","names":[],"sources":["../../../../src/components/Header/HeaderNavigationTabs.tsx"],"sourcesContent":["import { type JSX, useEffect, useRef } from 'react'\nimport { useWidgetEvents } from '../../hooks/useWidgetEvents.js'\nimport { useNavigationTabLabel } from '../../stores/navigationTabs/useNavigationTabLabel.js'\nimport { useNavigationTabsStore } from '../../stores/navigationTabs/useNavigationTabsStore.js'\nimport { WidgetEvent } from '../../types/events.js'\nimport type { NavigationTabKey } from '../../types/widget.js'\nimport { HeaderTabs } from './HeaderTabs.js'\n\nexport const HeaderNavigationTabs = (): JSX.Element | null => {\n const [tabs, activeTab, setActiveTab] = useNavigationTabsStore((store) => [\n store.tabs,\n store.activeTab,\n store.setActiveTab,\n ])\n const getLabel = useNavigationTabLabel()\n const emitter = useWidgetEvents()\n const previousTabRef = useRef<NavigationTabKey | undefined>(undefined)\n\n // Emit on the first resolved tab and on every subsequent switch.\n useEffect(() => {\n if (!activeTab || previousTabRef.current === activeTab) {\n return\n }\n emitter.emit(WidgetEvent.NavigationTabChanged, {\n tab: activeTab,\n previousTab: previousTabRef.current,\n })\n previousTabRef.current = activeTab\n }, [emitter, activeTab])\n\n if (!tabs.length || !activeTab) {\n return null\n }\n\n return (\n <HeaderTabs\n tabs={tabs.map((key) => ({ key, label: getLabel(key) }))}\n value={activeTab}\n onChange={setActiveTab}\n />\n )\n}\n"],"mappings":";;;;;;;;AAQA,MAAa,6BAAiD;CAC5D,MAAM,CAAC,MAAM,WAAW,gBAAgB,wBAAwB,UAAU;EACxE,MAAM;EACN,MAAM;EACN,MAAM;CACR,CAAC;CACD,MAAM,WAAW,sBAAsB;CACvC,MAAM,UAAU,gBAAgB;CAChC,MAAM,iBAAiB,OAAqC,KAAA,CAAS;CAGrE,gBAAgB;EACd,IAAI,CAAC,aAAa,eAAe,YAAY,WAC3C;EAEF,QAAQ,KAAA,wBAAuC;GAC7C,KAAK;GACL,aAAa,eAAe;EAC9B,CAAC;EACD,eAAe,UAAU;CAC3B,GAAG,CAAC,SAAS,SAAS,CAAC;CAEvB,IAAI,CAAC,KAAK,UAAU,CAAC,WACnB,OAAO;CAGT,OACE,oBAAC,YAAD;EACE,MAAM,KAAK,KAAK,SAAS;GAAE;GAAK,OAAO,SAAS,GAAG;EAAE,EAAE;EACvD,OAAO;EACP,UAAU;CACX,CAAA;AAEL"}
@@ -52,6 +52,6 @@ const IconCircleRoot = styled(Box, { shouldForwardProp: (prop) => prop !== "colo
52
52
  };
53
53
  });
54
54
  //#endregion
55
- export { IconCircleRoot, getStatusColor };
55
+ export { IconCircleRoot, getStatusColor, iconSizeRatio };
56
56
 
57
57
  //# sourceMappingURL=IconCircle.style.js.map