@lifi/widget 3.23.3 → 3.24.0-alpha.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 (136) hide show
  1. package/LICENSE +165 -0
  2. package/README.md +4 -8
  3. package/dist/esm/components/AppContainer.js +22 -8
  4. package/dist/esm/components/AppContainer.js.map +1 -1
  5. package/dist/esm/components/Avatar/Avatar.d.ts +6 -1
  6. package/dist/esm/components/Avatar/Avatar.js +4 -4
  7. package/dist/esm/components/Avatar/Avatar.js.map +1 -1
  8. package/dist/esm/components/Avatar/Avatar.style.d.ts +13 -4
  9. package/dist/esm/components/Avatar/Avatar.style.js +20 -10
  10. package/dist/esm/components/Avatar/Avatar.style.js.map +1 -1
  11. package/dist/esm/components/Avatar/SmallAvatar.d.ts +8 -2
  12. package/dist/esm/components/Avatar/SmallAvatar.js +7 -5
  13. package/dist/esm/components/Avatar/SmallAvatar.js.map +1 -1
  14. package/dist/esm/components/Avatar/TokenAvatar.d.ts +6 -0
  15. package/dist/esm/components/Avatar/TokenAvatar.js +7 -7
  16. package/dist/esm/components/Avatar/TokenAvatar.js.map +1 -1
  17. package/dist/esm/components/Avatar/utils.d.ts +1 -8
  18. package/dist/esm/components/Avatar/utils.js +5 -8
  19. package/dist/esm/components/Avatar/utils.js.map +1 -1
  20. package/dist/esm/components/BaseTransactionButton/BaseTransactionButton.js +6 -1
  21. package/dist/esm/components/BaseTransactionButton/BaseTransactionButton.js.map +1 -1
  22. package/dist/esm/components/Header/Header.style.d.ts +4 -1
  23. package/dist/esm/components/Header/Header.style.js +7 -5
  24. package/dist/esm/components/Header/Header.style.js.map +1 -1
  25. package/dist/esm/components/RouteCard/RouteCard.js +3 -3
  26. package/dist/esm/components/RouteCard/RouteCard.js.map +1 -1
  27. package/dist/esm/components/RouteCard/RouteCardEssentials.js +7 -13
  28. package/dist/esm/components/RouteCard/RouteCardEssentials.js.map +1 -1
  29. package/dist/esm/components/Routes/RoutesExpanded.js.map +1 -1
  30. package/dist/esm/components/SelectTokenButton/SelectTokenButton.style.js +2 -2
  31. package/dist/esm/components/SendToWallet/SendToWalletButton.js +2 -1
  32. package/dist/esm/components/SendToWallet/SendToWalletButton.js.map +1 -1
  33. package/dist/esm/components/StepActions/StepActions.js +3 -3
  34. package/dist/esm/components/StepActions/StepActions.style.d.ts +4 -1
  35. package/dist/esm/components/TokenList/TokenDetailsSheet.d.ts +6 -0
  36. package/dist/esm/components/TokenList/TokenDetailsSheet.js +24 -0
  37. package/dist/esm/components/TokenList/TokenDetailsSheet.js.map +1 -0
  38. package/dist/esm/components/TokenList/TokenDetailsSheetContent.d.ts +8 -0
  39. package/dist/esm/components/TokenList/TokenDetailsSheetContent.js +67 -0
  40. package/dist/esm/components/TokenList/TokenDetailsSheetContent.js.map +1 -0
  41. package/dist/esm/components/TokenList/TokenDetailsSheetContent.style.d.ts +5 -0
  42. package/dist/esm/components/TokenList/TokenDetailsSheetContent.style.js +28 -0
  43. package/dist/esm/components/TokenList/TokenDetailsSheetContent.style.js.map +1 -0
  44. package/dist/esm/components/TokenList/TokenListItem.js +29 -11
  45. package/dist/esm/components/TokenList/TokenListItem.js.map +1 -1
  46. package/dist/esm/components/TokenList/VirtualizedTokenList.js +45 -40
  47. package/dist/esm/components/TokenList/VirtualizedTokenList.js.map +1 -1
  48. package/dist/esm/components/TokenList/types.d.ts +7 -0
  49. package/dist/esm/components/TransactionDetails.js +7 -7
  50. package/dist/esm/components/TransactionDetails.js.map +1 -1
  51. package/dist/esm/config/version.d.ts +1 -1
  52. package/dist/esm/config/version.js +1 -1
  53. package/dist/esm/config/version.js.map +1 -1
  54. package/dist/esm/i18n/bn.json +5 -0
  55. package/dist/esm/i18n/de.json +5 -0
  56. package/dist/esm/i18n/en.json +4 -0
  57. package/dist/esm/i18n/es.json +5 -0
  58. package/dist/esm/i18n/fr.json +5 -0
  59. package/dist/esm/i18n/hi.json +5 -0
  60. package/dist/esm/i18n/id.json +5 -0
  61. package/dist/esm/i18n/it.json +5 -0
  62. package/dist/esm/i18n/ja.json +5 -0
  63. package/dist/esm/i18n/ko.json +5 -0
  64. package/dist/esm/i18n/pt.json +5 -0
  65. package/dist/esm/i18n/th.json +5 -0
  66. package/dist/esm/i18n/tr.json +5 -0
  67. package/dist/esm/i18n/uk.json +5 -0
  68. package/dist/esm/i18n/vi.json +5 -0
  69. package/dist/esm/i18n/zh.json +5 -0
  70. package/dist/esm/icons/lifi.d.ts +1 -1
  71. package/dist/esm/icons/lifi.js +1 -1
  72. package/dist/esm/icons/lifi.js.map +1 -1
  73. package/dist/esm/pages/ActiveTransactionsPage/ActiveTransactionsPage.js +1 -1
  74. package/dist/esm/pages/ActiveTransactionsPage/ActiveTransactionsPage.js.map +1 -1
  75. package/dist/esm/pages/RoutesPage/RoutesPage.js +1 -1
  76. package/dist/esm/pages/RoutesPage/RoutesPage.js.map +1 -1
  77. package/dist/esm/pages/SelectChainPage/SelectChainPage.js +1 -1
  78. package/dist/esm/pages/SelectChainPage/SelectChainPage.js.map +1 -1
  79. package/dist/esm/pages/SelectEnabledToolsPage.js +1 -1
  80. package/dist/esm/pages/SelectEnabledToolsPage.js.map +1 -1
  81. package/dist/esm/pages/TransactionHistoryPage/TransactionHistoryPage.js +1 -1
  82. package/dist/esm/pages/TransactionHistoryPage/TransactionHistoryPage.js.map +1 -1
  83. package/dist/esm/pages/TransactionPage/TokenValueBottomSheet.js +4 -4
  84. package/dist/esm/pages/TransactionPage/TokenValueBottomSheet.js.map +1 -1
  85. package/dist/esm/utils/format.d.ts +1 -0
  86. package/dist/esm/utils/format.js +16 -0
  87. package/dist/esm/utils/format.js.map +1 -1
  88. package/package.json +14 -14
  89. package/package.json.tmp +14 -14
  90. package/src/components/AppContainer.tsx +26 -8
  91. package/src/components/Avatar/Avatar.style.tsx +23 -11
  92. package/src/components/Avatar/Avatar.tsx +11 -6
  93. package/src/components/Avatar/SmallAvatar.tsx +13 -5
  94. package/src/components/Avatar/TokenAvatar.tsx +38 -8
  95. package/src/components/Avatar/utils.ts +5 -10
  96. package/src/components/BaseTransactionButton/BaseTransactionButton.tsx +5 -1
  97. package/src/components/Header/Header.style.ts +10 -6
  98. package/src/components/RouteCard/RouteCard.tsx +3 -3
  99. package/src/components/RouteCard/RouteCardEssentials.tsx +7 -14
  100. package/src/components/Routes/RoutesExpanded.tsx +7 -2
  101. package/src/components/SelectTokenButton/SelectTokenButton.style.tsx +2 -2
  102. package/src/components/SendToWallet/SendToWalletButton.tsx +3 -2
  103. package/src/components/StepActions/StepActions.tsx +3 -3
  104. package/src/components/TokenList/TokenDetailsSheet.tsx +48 -0
  105. package/src/components/TokenList/TokenDetailsSheetContent.style.tsx +34 -0
  106. package/src/components/TokenList/TokenDetailsSheetContent.tsx +200 -0
  107. package/src/components/TokenList/TokenListItem.tsx +117 -50
  108. package/src/components/TokenList/VirtualizedTokenList.tsx +95 -74
  109. package/src/components/TokenList/types.ts +8 -0
  110. package/src/components/TransactionDetails.tsx +8 -8
  111. package/src/config/version.ts +1 -1
  112. package/src/i18n/bn.json +5 -0
  113. package/src/i18n/de.json +5 -0
  114. package/src/i18n/en.json +4 -0
  115. package/src/i18n/es.json +5 -0
  116. package/src/i18n/fr.json +5 -0
  117. package/src/i18n/hi.json +5 -0
  118. package/src/i18n/id.json +5 -0
  119. package/src/i18n/it.json +5 -0
  120. package/src/i18n/ja.json +5 -0
  121. package/src/i18n/ko.json +5 -0
  122. package/src/i18n/pt.json +5 -0
  123. package/src/i18n/th.json +5 -0
  124. package/src/i18n/tr.json +5 -0
  125. package/src/i18n/uk.json +5 -0
  126. package/src/i18n/vi.json +5 -0
  127. package/src/i18n/zh.json +5 -0
  128. package/src/icons/lifi.ts +1 -1
  129. package/src/pages/ActiveTransactionsPage/ActiveTransactionsPage.tsx +1 -0
  130. package/src/pages/RoutesPage/RoutesPage.tsx +1 -1
  131. package/src/pages/SelectChainPage/SelectChainPage.tsx +1 -1
  132. package/src/pages/SelectEnabledToolsPage.tsx +1 -1
  133. package/src/pages/TransactionHistoryPage/TransactionHistoryPage.tsx +1 -0
  134. package/src/pages/TransactionPage/TokenValueBottomSheet.tsx +4 -4
  135. package/src/utils/format.ts +19 -0
  136. package/LICENSE.md +0 -201
@@ -30,7 +30,11 @@ export const BaseTransactionButton: React.FC<BaseTransactionButtonProps> = ({
30
30
  } else if (walletConfig?.onConnect) {
31
31
  walletConfig.onConnect()
32
32
  } else {
33
- openWalletMenu()
33
+ if (missingChain) {
34
+ openWalletMenu({ chain: missingChain })
35
+ } else {
36
+ openWalletMenu()
37
+ }
34
38
  }
35
39
  }
36
40
 
@@ -7,7 +7,7 @@ import {
7
7
  styled,
8
8
  } from '@mui/material'
9
9
  import type { WidgetSubvariant } from '../../types/widget.js'
10
- import { avatarMask12 } from '../Avatar/utils.js'
10
+ import { getAvatarMask } from '../Avatar/utils.js'
11
11
 
12
12
  export const HeaderAppBar = styled(AppBar)(({ theme }) => ({
13
13
  backgroundColor: 'transparent',
@@ -101,8 +101,12 @@ export const HeaderControlsContainer = styled(Box)(({ theme }) => ({
101
101
  }),
102
102
  }))
103
103
 
104
- export const WalletAvatar = styled(Avatar)(() => ({
105
- mask: avatarMask12,
106
- width: 24,
107
- height: 24,
108
- }))
104
+ export const WalletAvatar = styled(Avatar, {
105
+ shouldForwardProp: (prop) => prop !== 'avatarSize' && prop !== 'badgeSize',
106
+ })<{ avatarSize?: number; badgeSize?: number }>(
107
+ ({ avatarSize = 24, badgeSize = 12 }) => ({
108
+ mask: getAvatarMask(badgeSize),
109
+ width: avatarSize,
110
+ height: avatarSize,
111
+ })
112
+ )
@@ -1,5 +1,5 @@
1
1
  import type { TokenAmount } from '@lifi/sdk'
2
- import { isGaslessStep } from '@lifi/sdk'
2
+ import { isRelayerStep } from '@lifi/sdk'
3
3
  import ExpandLess from '@mui/icons-material/ExpandLess'
4
4
  import ExpandMore from '@mui/icons-material/ExpandMore'
5
5
  import { Box, Collapse } from '@mui/material'
@@ -53,8 +53,8 @@ export const RouteCard: React.FC<
53
53
  (tag) => tag === 'CHEAPEST' || tag === 'FASTEST'
54
54
  )
55
55
  const tags: string[] = mainTag ? [mainTag] : []
56
- const hasGaslessSupport = route.steps.some(isGaslessStep)
57
- if (hasGaslessSupport) {
56
+ const hasRelayerSupport = route.steps.every(isRelayerStep)
57
+ if (hasRelayerSupport) {
58
58
  tags.push('GASLESS')
59
59
  }
60
60
 
@@ -1,9 +1,10 @@
1
- import { isGaslessStep } from '@lifi/sdk'
1
+ import { isRelayerStep } from '@lifi/sdk'
2
2
  import AccessTimeFilled from '@mui/icons-material/AccessTimeFilled'
3
3
  import LocalGasStationRounded from '@mui/icons-material/LocalGasStationRounded'
4
4
  import { Box, Tooltip, Typography } from '@mui/material'
5
5
  import { useTranslation } from 'react-i18next'
6
6
  import { getAccumulatedFeeCostsBreakdown } from '../../utils/fees.js'
7
+ import { formatDuration } from '../../utils/format.js'
7
8
  import { FeeBreakdownTooltip } from '../FeeBreakdownTooltip.js'
8
9
  import { IconTypography } from '../IconTypography.js'
9
10
  import { TokenRate } from '../TokenRate/TokenRate.js'
@@ -19,10 +20,9 @@ export const RouteCardEssentials: React.FC<RouteCardEssentialsProps> = ({
19
20
  0
20
21
  )
21
22
  )
22
- const executionTimeMinutes = Math.floor(executionTimeSeconds / 60)
23
23
  const { gasCosts, feeCosts, combinedFeesUSD } =
24
24
  getAccumulatedFeeCostsBreakdown(route)
25
- const hasGaslessSupport = route.steps.some(isGaslessStep)
25
+ const hasRelayerSupport = route.steps.every(isRelayerStep)
26
26
  return (
27
27
  <Box
28
28
  sx={{
@@ -43,7 +43,7 @@ export const RouteCardEssentials: React.FC<RouteCardEssentialsProps> = ({
43
43
  <FeeBreakdownTooltip
44
44
  gasCosts={gasCosts}
45
45
  feeCosts={feeCosts}
46
- relayerSupport={hasGaslessSupport}
46
+ relayerSupport={hasRelayerSupport}
47
47
  >
48
48
  <Box
49
49
  sx={{
@@ -56,7 +56,7 @@ export const RouteCardEssentials: React.FC<RouteCardEssentialsProps> = ({
56
56
  <LocalGasStationRounded fontSize="inherit" />
57
57
  </IconTypography>
58
58
  <Typography
59
- data-value={hasGaslessSupport ? 0 : combinedFeesUSD}
59
+ data-value={hasRelayerSupport ? 0 : combinedFeesUSD}
60
60
  sx={{
61
61
  fontSize: 14,
62
62
  color: 'text.primary',
@@ -64,7 +64,7 @@ export const RouteCardEssentials: React.FC<RouteCardEssentialsProps> = ({
64
64
  lineHeight: 1,
65
65
  }}
66
66
  >
67
- {hasGaslessSupport
67
+ {hasRelayerSupport
68
68
  ? t('main.fees.free')
69
69
  : t('format.currency', {
70
70
  value: combinedFeesUSD,
@@ -90,14 +90,7 @@ export const RouteCardEssentials: React.FC<RouteCardEssentialsProps> = ({
90
90
  lineHeight: 1,
91
91
  }}
92
92
  >
93
- {(executionTimeSeconds < 60
94
- ? executionTimeSeconds
95
- : executionTimeMinutes
96
- ).toLocaleString(i18n.language, {
97
- style: 'unit',
98
- unit: executionTimeSeconds < 60 ? 'second' : 'minute',
99
- unitDisplay: 'narrow',
100
- })}
93
+ {formatDuration(executionTimeSeconds, i18n.language)}
101
94
  </Typography>
102
95
  </Box>
103
96
  </Tooltip>
@@ -127,12 +127,17 @@ export const RoutesExpandedElement = () => {
127
127
 
128
128
  return (
129
129
  <RoutesExpandedCollapse
130
- timeout={timeout.enter}
130
+ timeout={timeout.enter as number}
131
131
  in={expanded}
132
132
  orientation="horizontal"
133
133
  onExited={onExit}
134
134
  >
135
- <Grow timeout={timeout.enter} in={expanded} mountOnEnter unmountOnExit>
135
+ <Grow
136
+ timeout={timeout.enter as number}
137
+ in={expanded}
138
+ mountOnEnter
139
+ unmountOnExit
140
+ >
136
141
  <Container enableColorScheme minimumHeight={isLoading}>
137
142
  <ScrollableContainer>
138
143
  <Header>
@@ -106,9 +106,9 @@ export const CardContent = styled(MuiCardContent, {
106
106
  ...(cardVariant === 'filled' && {
107
107
  '&:hover': {
108
108
  cursor: 'pointer',
109
- backgroundColor: `color-mix(in srgb, ${theme.vars.palette.background.paper} 98%, white)`,
109
+ backgroundColor: `color-mix(in srgb, ${theme.vars.palette.background.paper} 98%, black)`,
110
110
  ...theme.applyStyles('dark', {
111
- backgroundColor: `color-mix(in srgb, ${theme.vars.palette.background.paper} 98%, black)`,
111
+ backgroundColor: `color-mix(in srgb, ${theme.vars.palette.background.paper} 98%, white)`,
112
112
  }),
113
113
  },
114
114
  }),
@@ -113,9 +113,10 @@ export const SendToWalletButton: React.FC<CardProps> = (props) => {
113
113
  // Timeout is needed here to push the collapseTransitionTime update to the back of the event loop so that it doesn't fired too quickly
114
114
  // biome-ignore lint/correctness/useExhaustiveDependencies:
115
115
  useEffect(() => {
116
- setTimeout(() => {
116
+ const timeout = setTimeout(() => {
117
117
  collapseTransitionTime.current = 225
118
118
  }, 0)
119
+ return () => clearTimeout(timeout)
119
120
  }, [collapseTransitionTime])
120
121
 
121
122
  const isOpenCollapse =
@@ -128,7 +129,7 @@ export const SendToWalletButton: React.FC<CardProps> = (props) => {
128
129
 
129
130
  return (
130
131
  <Collapse
131
- timeout={collapseTransitionTime.current}
132
+ timeout={collapseTransitionTime.current as number}
132
133
  in={isOpenCollapse}
133
134
  mountOnEnter
134
135
  unmountOnExit
@@ -1,5 +1,5 @@
1
1
  import type { LiFiStep, StepExtended } from '@lifi/sdk'
2
- import { isGaslessStep } from '@lifi/sdk'
2
+ import { isRelayerStep } from '@lifi/sdk'
3
3
  import ArrowForward from '@mui/icons-material/ArrowForward'
4
4
  import ExpandLess from '@mui/icons-material/ExpandLess'
5
5
  import ExpandMore from '@mui/icons-material/ExpandMore'
@@ -162,7 +162,7 @@ export const IncludedSteps: React.FC<IncludedStepsProps> = ({ step }) => {
162
162
  ) : null
163
163
  }
164
164
 
165
- const hasGaslessSupport = isGaslessStep(step)
165
+ const hasRelayerSupport = isRelayerStep(step)
166
166
 
167
167
  return (
168
168
  <Box
@@ -194,7 +194,7 @@ export const IncludedSteps: React.FC<IncludedStepsProps> = ({ step }) => {
194
194
  <ProtocolStepDetailsLabel
195
195
  step={step}
196
196
  feeConfig={feeConfig}
197
- relayerSupport={hasGaslessSupport}
197
+ relayerSupport={hasRelayerSupport}
198
198
  />
199
199
  ) : (
200
200
  <SwapStepDetailsLabel step={step} />
@@ -0,0 +1,48 @@
1
+ import { forwardRef, useImperativeHandle, useRef, useState } from 'react'
2
+ import { BottomSheet } from '../BottomSheet/BottomSheet.js'
3
+ import type { BottomSheetBase } from '../BottomSheet/types.js'
4
+ import { TokenDetailsSheetContent } from './TokenDetailsSheetContent.js'
5
+ import type { TokenDetailsSheetBase } from './types.js'
6
+ interface TokenDetailsSheetProps {
7
+ chainId: number | undefined
8
+ }
9
+
10
+ export const TokenDetailsSheet = forwardRef<
11
+ TokenDetailsSheetBase,
12
+ TokenDetailsSheetProps
13
+ >(({ chainId }, ref) => {
14
+ const bottomSheetRef = useRef<BottomSheetBase>(null)
15
+ const [tokenAddress, setTokenAddress] = useState<string | undefined>(
16
+ undefined
17
+ )
18
+ const [withoutContractAddress, setWithoutContractAddress] = useState(false)
19
+
20
+ useImperativeHandle(
21
+ ref,
22
+ () => ({
23
+ isOpen: () => bottomSheetRef.current?.isOpen(),
24
+ open: (address: string, noContractAddress: boolean) => {
25
+ setTokenAddress(address)
26
+ setWithoutContractAddress(noContractAddress)
27
+ bottomSheetRef.current?.open()
28
+ },
29
+ close: () => {
30
+ bottomSheetRef.current?.close()
31
+ setTokenAddress(undefined)
32
+ setWithoutContractAddress(false)
33
+ },
34
+ }),
35
+ []
36
+ )
37
+
38
+ return (
39
+ <BottomSheet ref={bottomSheetRef}>
40
+ <TokenDetailsSheetContent
41
+ ref={ref}
42
+ tokenAddress={tokenAddress}
43
+ withoutContractAddress={withoutContractAddress}
44
+ chainId={chainId}
45
+ />
46
+ </BottomSheet>
47
+ )
48
+ })
@@ -0,0 +1,34 @@
1
+ import { Box, Typography, styled } from '@mui/material'
2
+ import type { PageContainerProps } from '../PageContainer.js'
3
+ import { PageContainer } from '../PageContainer.js'
4
+
5
+ export const TokenDetailsSheetContainer = styled(
6
+ PageContainer
7
+ )<PageContainerProps>(({ theme }) => ({
8
+ display: 'flex',
9
+ flexDirection: 'column',
10
+ gap: theme.spacing(2),
11
+ paddingTop: theme.spacing(3),
12
+ paddingBottom: theme.spacing(3),
13
+ }))
14
+
15
+ export const TokenDetailsSheetHeader = styled(Box)(({ theme }) => ({
16
+ display: 'flex',
17
+ flexDirection: 'row',
18
+ alignItems: 'flex-start',
19
+ justifyContent: 'space-between',
20
+ gap: theme.spacing(2),
21
+ }))
22
+
23
+ export const Label = styled(Typography)(({ theme }) => ({
24
+ fontWeight: 500,
25
+ fontSize: '12px',
26
+ lineHeight: '16px',
27
+ color: theme.vars.palette.text.secondary,
28
+ }))
29
+
30
+ export const MetricContainer = styled(Box)(({ theme }) => ({
31
+ display: 'flex',
32
+ flexDirection: 'column',
33
+ gap: theme.spacing(1),
34
+ }))
@@ -0,0 +1,200 @@
1
+ import Close from '@mui/icons-material/Close'
2
+ import ContentCopyRounded from '@mui/icons-material/ContentCopyRounded'
3
+ import OpenInNewRounded from '@mui/icons-material/OpenInNewRounded'
4
+ import { Box, IconButton, Link, Skeleton, Typography } from '@mui/material'
5
+ import { type PropsWithChildren, forwardRef, useMemo } from 'react'
6
+ import { useTranslation } from 'react-i18next'
7
+ import { useAvailableChains } from '../../hooks/useAvailableChains.js'
8
+ import { useExplorer } from '../../hooks/useExplorer.js'
9
+ import { useTokenSearch } from '../../hooks/useTokenSearch.js'
10
+ import { formatTokenPrice } from '../../utils/format.js'
11
+ import { shortenAddress } from '../../utils/wallet.js'
12
+ import { TokenAvatar } from '../Avatar/TokenAvatar.js'
13
+ import { CardIconButton } from '../Card/CardIconButton.js'
14
+ import {
15
+ Label,
16
+ MetricContainer,
17
+ TokenDetailsSheetContainer,
18
+ TokenDetailsSheetHeader,
19
+ } from './TokenDetailsSheetContent.style.js'
20
+ import type { TokenDetailsSheetBase } from './types.js'
21
+
22
+ interface TokenDetailsSheetContentProps {
23
+ tokenAddress: string | undefined
24
+ chainId: number | undefined
25
+ withoutContractAddress: boolean
26
+ }
27
+
28
+ const noDataLabel = '-'
29
+
30
+ export const TokenDetailsSheetContent = forwardRef<
31
+ TokenDetailsSheetBase,
32
+ TokenDetailsSheetContentProps
33
+ >(({ tokenAddress, chainId, withoutContractAddress }, ref) => {
34
+ const { t } = useTranslation()
35
+ const { getAddressLink } = useExplorer()
36
+ const { getChainById } = useAvailableChains()
37
+
38
+ const { token, isLoading } = useTokenSearch(
39
+ chainId,
40
+ tokenAddress,
41
+ !!tokenAddress
42
+ )
43
+ const chain = useMemo(() => getChainById(chainId), [chainId, getChainById])
44
+
45
+ const copyContractAddress = async (e: React.MouseEvent) => {
46
+ e.stopPropagation()
47
+ try {
48
+ // Clipboard API may throw if access is denied (e.g., in insecure contexts or older browsers)
49
+ await navigator.clipboard.writeText(tokenAddress || '')
50
+ } catch {
51
+ // Silently fail to avoid crashing the UI if clipboard write fails
52
+ }
53
+ }
54
+
55
+ return (
56
+ <TokenDetailsSheetContainer>
57
+ <TokenDetailsSheetHeader>
58
+ <Box
59
+ sx={{
60
+ display: 'flex',
61
+ flexDirection: 'row',
62
+ alignItems: 'center',
63
+ gap: 3,
64
+ }}
65
+ >
66
+ <TokenAvatar
67
+ token={token}
68
+ chain={chain}
69
+ tokenAvatarSize={72}
70
+ chainAvatarSize={28}
71
+ isLoading={isLoading}
72
+ />
73
+ <MetricContainer>
74
+ {isLoading ? (
75
+ <>
76
+ <Skeleton variant="rounded" width={80} height={24} />
77
+ <Skeleton variant="rounded" width={80} height={16} />
78
+ </>
79
+ ) : (
80
+ <>
81
+ <Typography
82
+ fontWeight={700}
83
+ fontSize="24px"
84
+ lineHeight="24px"
85
+ color="text.primary"
86
+ >
87
+ {token?.symbol || noDataLabel}
88
+ </Typography>
89
+ <Label>{token?.name || noDataLabel}</Label>
90
+ </>
91
+ )}
92
+ </MetricContainer>
93
+ </Box>
94
+ <IconButton
95
+ onClick={(e) => {
96
+ e.stopPropagation()
97
+ if (ref && typeof ref !== 'function') {
98
+ ref.current?.close()
99
+ }
100
+ }}
101
+ sx={{ mt: '-8px', mr: '-8px' }}
102
+ >
103
+ <Close />
104
+ </IconButton>
105
+ </TokenDetailsSheetHeader>
106
+ <MetricWithSkeleton
107
+ isLoading={isLoading}
108
+ label={t('tokenMetric.currentPrice')}
109
+ width={200}
110
+ height={40}
111
+ >
112
+ <Typography
113
+ sx={{
114
+ fontWeight: 700,
115
+ fontSize: '32px',
116
+ lineHeight: '40px',
117
+ color: 'text.primary',
118
+ }}
119
+ >
120
+ {token
121
+ ? t('format.currency', {
122
+ value: formatTokenPrice('1', token.priceUSD, token.decimals),
123
+ })
124
+ : noDataLabel}
125
+ </Typography>
126
+ </MetricWithSkeleton>
127
+ {!withoutContractAddress && (
128
+ <MetricWithSkeleton
129
+ isLoading={isLoading}
130
+ label={t('tokenMetric.contractAddress')}
131
+ width={200}
132
+ height={24}
133
+ >
134
+ <Box
135
+ sx={{
136
+ display: 'flex',
137
+ flexDirection: 'row',
138
+ alignItems: 'center',
139
+ gap: 1,
140
+ }}
141
+ >
142
+ <Typography
143
+ sx={{
144
+ fontWeight: 700,
145
+ fontSize: '18px',
146
+ lineHeight: '24px',
147
+ color: 'text.primary',
148
+ }}
149
+ >
150
+ {shortenAddress(tokenAddress)}
151
+ </Typography>
152
+ {tokenAddress && (
153
+ <CardIconButton size="small" onClick={copyContractAddress}>
154
+ <ContentCopyRounded fontSize="inherit" />
155
+ </CardIconButton>
156
+ )}
157
+ {tokenAddress && (
158
+ <CardIconButton
159
+ size="small"
160
+ LinkComponent={Link}
161
+ href={getAddressLink(tokenAddress, chainId)}
162
+ target="_blank"
163
+ rel="nofollow noreferrer"
164
+ onClick={(e) => e.stopPropagation()}
165
+ >
166
+ <OpenInNewRounded fontSize="inherit" />
167
+ </CardIconButton>
168
+ )}
169
+ </Box>
170
+ </MetricWithSkeleton>
171
+ )}
172
+ </TokenDetailsSheetContainer>
173
+ )
174
+ })
175
+
176
+ interface MetricWithSkeletonProps {
177
+ label: string
178
+ isLoading: boolean
179
+ width: number
180
+ height: number
181
+ }
182
+
183
+ const MetricWithSkeleton = ({
184
+ label,
185
+ width,
186
+ height,
187
+ isLoading,
188
+ children,
189
+ }: PropsWithChildren<MetricWithSkeletonProps>) => {
190
+ return (
191
+ <MetricContainer>
192
+ <Label>{label}</Label>
193
+ {isLoading ? (
194
+ <Skeleton variant="rounded" width={width} height={height} />
195
+ ) : (
196
+ children
197
+ )}
198
+ </MetricContainer>
199
+ )
200
+ }