From 6798bf3cf1a4bb28ba0f629cc6a48167d7b8bee9 Mon Sep 17 00:00:00 2001 From: Charles Bachmeier Date: Fri, 20 Oct 2023 13:11:22 -0700 Subject: [PATCH] feat: [info] add PDP loading skeleton (#7494) * initial skeleton setup * responsive table skeleton * correct table widht * right side column added * add comments * move loading components to their corresponding component * remove extra bubble and adjust some styles * move table skeleton to its own file * add shared styles and skele file * add loading skeleton tests * design style nits * update tests * bips_base * fix regression --- .../PoolDetails/PoolDetailsHeader.test.tsx | 7 + src/pages/PoolDetails/PoolDetailsHeader.tsx | 24 + src/pages/PoolDetails/PoolDetailsStats.tsx | 75 +- .../PoolDetailsStatsButtons.test.tsx | 7 + .../PoolDetails/PoolDetailsStatsButtons.tsx | 21 +- .../PoolDetails/PoolDetailsTableSkeleton.tsx | 98 ++ .../PoolDetailsHeader.test.tsx.snap | 115 ++ .../PoolDetailsStatsButtons.test.tsx.snap | 70 + .../__snapshots__/index.test.tsx.snap | 1503 ++++++++++++++--- src/pages/PoolDetails/index.test.tsx | 3 +- src/pages/PoolDetails/index.tsx | 99 +- src/pages/PoolDetails/shared.ts | 13 + src/pages/RouteDefinitions.tsx | 6 +- 13 files changed, 1720 insertions(+), 321 deletions(-) create mode 100644 src/pages/PoolDetails/PoolDetailsTableSkeleton.tsx create mode 100644 src/pages/PoolDetails/shared.ts diff --git a/src/pages/PoolDetails/PoolDetailsHeader.test.tsx b/src/pages/PoolDetails/PoolDetailsHeader.test.tsx index 61ca771c64..6794976444 100644 --- a/src/pages/PoolDetails/PoolDetailsHeader.test.tsx +++ b/src/pages/PoolDetails/PoolDetailsHeader.test.tsx @@ -13,6 +13,13 @@ describe('PoolDetailsHeader', () => { toggleReversed: jest.fn(), } + it('loading skeleton is shown', () => { + const { asFragment } = render() + expect(asFragment()).toMatchSnapshot() + + expect(screen.getByTestId('pdp-header-loading-skeleton')).toBeInTheDocument() + }) + it('renders header text correctly', () => { const { asFragment } = render() expect(asFragment()).toMatchSnapshot() diff --git a/src/pages/PoolDetails/PoolDetailsHeader.tsx b/src/pages/PoolDetails/PoolDetailsHeader.tsx index 3589831b21..81672bcc2a 100644 --- a/src/pages/PoolDetails/PoolDetailsHeader.tsx +++ b/src/pages/PoolDetails/PoolDetailsHeader.tsx @@ -4,6 +4,7 @@ import blankTokenUrl from 'assets/svg/blank_token.svg' import Column from 'components/Column' import { ChainLogo } from 'components/Logo/ChainLogo' import Row from 'components/Row' +import { LoadingBubble } from 'components/Tokens/loading' import { BIPS_BASE } from 'constants/misc' import { chainIdToBackendName } from 'graphql/data/util' import { useCurrency } from 'hooks/Tokens' @@ -15,6 +16,7 @@ import { ClickableStyle, ThemedText } from 'theme/components' import { shortenAddress } from 'utils' import { ReversedArrowsIcon } from './icons' +import { DetailBubble } from './shared' const HeaderColumn = styled(Column)` gap: 36px; @@ -35,6 +37,12 @@ const ToggleReverseArrows = styled(ReversedArrowsIcon)` ${ClickableStyle} ` +const IconBubble = styled(LoadingBubble)` + width: 32px; + height: 32px; + border-radius: 50%; +` + interface Token { id: string symbol: string @@ -47,6 +55,7 @@ interface PoolDetailsHeaderProps { token1?: Token feeTier?: number toggleReversed: React.DispatchWithoutAction + loading?: boolean } export function PoolDetailsHeader({ @@ -56,10 +65,25 @@ export function PoolDetailsHeader({ token1, feeTier, toggleReversed, + loading, }: PoolDetailsHeaderProps) { const currencies = [useCurrency(token0?.id, chainId) ?? undefined, useCurrency(token1?.id, chainId) ?? undefined] const chainName = chainIdToBackendName(chainId) const origin = `/tokens/${chainName}` + + if (loading) + return ( + + + + + + + + + + ) + return ( diff --git a/src/pages/PoolDetails/PoolDetailsStats.tsx b/src/pages/PoolDetails/PoolDetailsStats.tsx index cbf58f9c5a..b83cb4c2d3 100644 --- a/src/pages/PoolDetails/PoolDetailsStats.tsx +++ b/src/pages/PoolDetails/PoolDetailsStats.tsx @@ -2,6 +2,7 @@ import { Trans } from '@lingui/macro' import Column from 'components/Column' import CurrencyLogo from 'components/Logo/CurrencyLogo' import Row from 'components/Row' +import { LoadingBubble } from 'components/Tokens/loading' import { DeltaArrow } from 'components/Tokens/TokenDetails/Delta' import { PoolData } from 'graphql/thegraph/PoolData' import { useCurrency } from 'hooks/Tokens' @@ -15,6 +16,8 @@ import { colors } from 'theme/colors' import { ThemedText } from 'theme/components' import { NumberType, useFormatter } from 'utils/formatNumbers' +import { DetailBubble } from './shared' + const HeaderText = styled(Text)` font-weight: 485; font-size: 24px; @@ -90,13 +93,25 @@ const BalanceChartSide = styled.div<{ percent: number; $color: string; isLeft: b ${({ isLeft }) => (isLeft ? leftBarChartStyles : rightBarChartStyles)} ` +const StatSectionBubble = styled(LoadingBubble)` + width: 180px; + height: 40px; +` + +const StatHeaderBubble = styled(LoadingBubble)` + width: 116px; + height: 24px; + border-radius: 8px; +` + interface PoolDetailsStatsProps { - poolData: PoolData - isReversed: boolean + poolData?: PoolData + isReversed?: boolean chainId?: number + loading?: boolean } -export function PoolDetailsStats({ poolData, isReversed, chainId }: PoolDetailsStatsProps) { +export function PoolDetailsStats({ poolData, isReversed, chainId, loading }: PoolDetailsStatsProps) { const isScreenSize = useScreenSize() const screenIsNotLarge = isScreenSize['lg'] const { formatNumber } = useFormatter() @@ -112,26 +127,46 @@ export function PoolDetailsStats({ poolData, isReversed, chainId }: PoolDetailsS } const [token0, token1] = useMemo(() => { - const fullWidth = poolData?.tvlToken0 / poolData?.token0Price + poolData?.tvlToken1 - const token0FullData = { - ...poolData?.token0, - price: poolData?.token0Price, - tvl: poolData?.tvlToken0, - color: color0, - percent: poolData?.tvlToken0 / poolData?.token0Price / fullWidth, - currency: currency0, + if (poolData) { + const fullWidth = poolData?.tvlToken0 / poolData?.token0Price + poolData?.tvlToken1 + const token0FullData = { + ...poolData?.token0, + price: poolData?.token0Price, + tvl: poolData?.tvlToken0, + color: color0, + percent: poolData?.tvlToken0 / poolData?.token0Price / fullWidth, + currency: currency0, + } + const token1FullData = { + ...poolData?.token1, + price: poolData?.token1Price, + tvl: poolData?.tvlToken1, + color: color1, + percent: poolData?.tvlToken1 / fullWidth, + currency: currency1, + } + return isReversed ? [token1FullData, token0FullData] : [token0FullData, token1FullData] + } else { + return [undefined, undefined] } - const token1FullData = { - ...poolData?.token1, - price: poolData?.token1Price, - tvl: poolData?.tvlToken1, - color: color1, - percent: poolData?.tvlToken1 / fullWidth, - currency: currency1, - } - return isReversed ? [token1FullData, token0FullData] : [token0FullData, token1FullData] }, [color0, color1, currency0, currency1, isReversed, poolData]) + if (loading || !token0 || !token1 || !poolData) { + return ( + + + + + {Array.from({ length: 4 }).map((_, i) => ( + + + + + ))} + + ) + } + return ( diff --git a/src/pages/PoolDetails/PoolDetailsStatsButtons.test.tsx b/src/pages/PoolDetails/PoolDetailsStatsButtons.test.tsx index 356a7e75d5..70e88857c5 100644 --- a/src/pages/PoolDetails/PoolDetailsStatsButtons.test.tsx +++ b/src/pages/PoolDetails/PoolDetailsStatsButtons.test.tsx @@ -26,6 +26,13 @@ describe('PoolDetailsStatsButton', () => { mocked(useMultiChainPositions).mockReturnValue(useMultiChainPositionsReturnValue) }) + it('loading skeleton shown correctly', () => { + const { asFragment } = render() + expect(asFragment()).toMatchSnapshot() + + expect(screen.getByTestId('pdp-buttons-loading-skeleton')).toBeVisible() + }) + it('renders both buttons correctly', () => { const { asFragment } = render() expect(asFragment()).toMatchSnapshot() diff --git a/src/pages/PoolDetails/PoolDetailsStatsButtons.tsx b/src/pages/PoolDetails/PoolDetailsStatsButtons.tsx index 497b202725..dae1bf8c42 100644 --- a/src/pages/PoolDetails/PoolDetailsStatsButtons.tsx +++ b/src/pages/PoolDetails/PoolDetailsStatsButtons.tsx @@ -4,6 +4,7 @@ import { PositionInfo } from 'components/AccountDrawer/MiniPortfolio/Pools/cache import useMultiChainPositions from 'components/AccountDrawer/MiniPortfolio/Pools/useMultiChainPositions' import { ButtonEmphasis, ButtonSize, ThemeButton } from 'components/Button' import Row from 'components/Row' +import { LoadingBubble } from 'components/Tokens/loading' import { Token } from 'graphql/thegraph/__generated__/types-and-hooks' import { useCurrency } from 'hooks/Tokens' import { useSwitchChain } from 'hooks/useSwitchChain' @@ -26,11 +27,18 @@ const PoolButton = styled(ThemeButton)` width: 50%; ` +const ButtonBubble = styled(LoadingBubble)` + height: 44px; + width: 175px; + border-radius: 900px; +` + interface PoolDetailsStatsButtonsProps { chainId?: number token0?: Token token1?: Token feeTier?: number + loading?: boolean } function findMatchingPosition(positions: PositionInfo[], token0?: Token, token1?: Token, feeTier?: number) { @@ -45,7 +53,7 @@ function findMatchingPosition(positions: PositionInfo[], token0?: Token, token1? ) } -export function PoolDetailsStatsButtons({ chainId, token0, token1, feeTier }: PoolDetailsStatsButtonsProps) { +export function PoolDetailsStatsButtons({ chainId, token0, token1, feeTier, loading }: PoolDetailsStatsButtonsProps) { const { chainId: walletChainId, connector, account } = useWeb3React() const { positions: userOwnedPositions } = useMultiChainPositions(account ?? '', chainId ? [chainId] : undefined) const position = userOwnedPositions && findMatchingPosition(userOwnedPositions, token0, token1, feeTier) @@ -64,7 +72,15 @@ export function PoolDetailsStatsButtons({ chainId, token0, token1, feeTier }: Po ) } } - if (!currency0 || !currency1) return null + + if (loading || !currency0 || !currency1) + return ( + + + + + ) + return ( Add liquidity - theme.surface3}; + padding-bottom: 12px; + overflow-y: hidden; + ${ScrollBarStyles} +` + +const TableRow = styled(Row)<{ $borderBottom?: boolean }>` + justify-content: space-between; + border-bottom: ${({ $borderBottom, theme }) => ($borderBottom ? `1px solid ${theme.surface3}` : 'none')}}; + padding: 12px; + min-width: max-content; +` + +const TableElement = styled(ThemedText.BodySecondary)<{ + alignRight?: boolean + small?: boolean + large?: boolean +}>` + display: flex; + padding: 0px 8px; + flex: ${({ small }) => (small ? 'unset' : '1')}; + width: ${({ small }) => (small ? '44px' : 'auto')}; + min-width: ${({ large, small }) => (large ? '136px' : small ? 'unset' : '121px')} !important; + justify-content: ${({ alignRight }) => (alignRight ? 'flex-end' : 'flex-start')}; +` +{ + /* TODO(WEB-2735): When making real datatable, merge in this code and deprecate this skeleton file */ +} +export function PoolDetailsTableSkeleton() { + return ( + + + + + + Time + + + + Type + + + USD + + + + + + + + + Maker + + + Txn + + + {Array.from({ length: 10 }).map((_, i) => ( + + + + + + + + + + + + + + + + + + + + + + + + ))} +
+ ) +} diff --git a/src/pages/PoolDetails/__snapshots__/PoolDetailsHeader.test.tsx.snap b/src/pages/PoolDetails/__snapshots__/PoolDetailsHeader.test.tsx.snap index 33be9190b3..5f635c21f0 100644 --- a/src/pages/PoolDetails/__snapshots__/PoolDetailsHeader.test.tsx.snap +++ b/src/pages/PoolDetails/__snapshots__/PoolDetailsHeader.test.tsx.snap @@ -1,5 +1,120 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`PoolDetailsHeader loading skeleton is shown 1`] = ` + + .c5 { + box-sizing: border-box; + margin: 0; + min-width: 0; +} + +.c6 { + width: 100%; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + padding: 0; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; + gap: 8px; +} + +.c0 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; +} + +.c4 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; + gap: 8px; +} + +.c2 { + border-radius: 12px; + border-radius: 12px; + height: 24px; + width: 50%; + width: 50%; + -webkit-animation: fAQEyV 1.5s infinite; + animation: fAQEyV 1.5s infinite; + -webkit-animation-fill-mode: both; + animation-fill-mode: both; + background: linear-gradient( to left, #22222212 25%, rgba(53,53,53,0.07) 50%, #22222212 75% ); + will-change: background-position; + background-size: 400%; +} + +.c3 { + height: 16px; + width: 300px; +} + +.c8 { + height: 16px; + width: 137px; +} + +.c1 { + gap: 36px; +} + +.c7 { + width: 32px; + height: 32px; + border-radius: 50%; +} + +
+
+
+
+
+
+
+
+
+ +`; + exports[`PoolDetailsHeader renders header text correctly 1`] = ` .c2 { diff --git a/src/pages/PoolDetails/__snapshots__/PoolDetailsStatsButtons.test.tsx.snap b/src/pages/PoolDetails/__snapshots__/PoolDetailsStatsButtons.test.tsx.snap index 2b8fcc5fb7..a6f743b439 100644 --- a/src/pages/PoolDetails/__snapshots__/PoolDetailsStatsButtons.test.tsx.snap +++ b/src/pages/PoolDetails/__snapshots__/PoolDetailsStatsButtons.test.tsx.snap @@ -1,5 +1,75 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`PoolDetailsStatsButton loading skeleton shown correctly 1`] = ` + + .c0 { + box-sizing: border-box; + margin: 0; + min-width: 0; +} + +.c1 { + width: 100%; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + padding: 0; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; +} + +.c3 { + border-radius: 12px; + border-radius: 12px; + height: 24px; + width: 50%; + width: 50%; + -webkit-animation: fAQEyV 1.5s infinite; + animation: fAQEyV 1.5s infinite; + -webkit-animation-fill-mode: both; + animation-fill-mode: both; + background: linear-gradient( to left, #22222212 25%, rgba(53,53,53,0.07) 50%, #22222212 75% ); + will-change: background-position; + background-size: 400%; +} + +.c2 { + gap: 12px; +} + +.c4 { + height: 44px; + width: 175px; + border-radius: 900px; +} + +@media (max-width:1023px) { + .c2 { + display: none; + } +} + +
+
+
+
+ +`; + exports[`PoolDetailsStatsButton renders both buttons correctly 1`] = ` .c0 { diff --git a/src/pages/PoolDetails/__snapshots__/index.test.tsx.snap b/src/pages/PoolDetails/__snapshots__/index.test.tsx.snap index 81a57f01ea..b4e98fb6e0 100644 --- a/src/pages/PoolDetails/__snapshots__/index.test.tsx.snap +++ b/src/pages/PoolDetails/__snapshots__/index.test.tsx.snap @@ -8,7 +8,7 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the min-width: 0; } -.c9 { +.c11 { box-sizing: border-box; margin: 0; min-width: 0; @@ -17,7 +17,7 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the width: max-content; } -.c31 { +.c52 { box-sizing: border-box; margin: 0; min-width: 0; @@ -44,7 +44,7 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the justify-content: flex-start; } -.c8 { +.c10 { width: 100%; display: -webkit-box; display: -webkit-flex; @@ -62,7 +62,7 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the gap: 18px; } -.c10 { +.c12 { width: -webkit-max-content; width: -moz-max-content; width: max-content; @@ -82,7 +82,7 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the gap: 8px; } -.c32 { +.c53 { width: -webkit-max-content; width: -moz-max-content; width: max-content; @@ -102,15 +102,15 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the padding: 4px 0px; } -.c6 { +.c8 { color: #7D7D7D; } -.c7 { +.c9 { color: #222222; } -.c43 { +.c64 { -webkit-text-decoration: none; text-decoration: none; cursor: pointer; @@ -121,11 +121,11 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the font-weight: 500; } -.c43:hover { +.c64:hover { opacity: 0.6; } -.c43:active { +.c64:active { opacity: 0.4; } @@ -143,98 +143,37 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the justify-content: flex-start; } -.c40 { - opacity: 0; - -webkit-transition: opacity 250ms ease-in; - transition: opacity 250ms ease-in; - width: 20px; - height: 20px; - border-radius: 50%; -} - -.c39 { - width: 20px; - height: 20px; - background: #22222212; - -webkit-transition: background-color 250ms ease-in; - transition: background-color 250ms ease-in; - box-shadow: 0 0 1px white; - border-radius: 50%; -} - -.c38 { - position: relative; +.c5 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; display: flex; -} - -.c46 { - color: #CECECE; - font-weight: 485; - font-size: 16px; -} - -.c36 { - gap: 12px; - width: 100%; -} - -.c37 { + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; gap: 8px; - width: 100%; } -.c41 { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; +.c19 { + border-radius: 12px; + border-radius: 12px; + height: 24px; + width: 50%; + width: 50%; + -webkit-animation: fAQEyV 1.5s infinite; + animation: fAQEyV 1.5s infinite; + -webkit-animation-fill-mode: both; + animation-fill-mode: both; + background: linear-gradient( to left, #22222212 25%, rgba(53,53,53,0.07) 50%, #22222212 75% ); + will-change: background-position; + background-size: 400%; } .c42 { - -webkit-flex-wrap: wrap; - -ms-flex-wrap: wrap; - flex-wrap: wrap; -} - -.c44 { - gap: 8px; - padding: 8px 12px; - border-radius: 20px; - color: #FC72FF; - background-color: #FC72FF1f; - font-size: 14px; - font-weight: 535; - line-height: 16px; - width: -webkit-max-content; - width: -moz-max-content; - width: max-content; - -webkit-text-decoration: none; - text-decoration: none; - cursor: pointer; - -webkit-transition-duration: 125ms; - transition-duration: 125ms; -} - -.c44:hover { - opacity: 0.6; -} - -.c44:active { - opacity: 0.4; -} - -.c45 { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - max-width: 100%; - line-height: 24px; - white-space: pre-wrap; -} - -.c21 { background-color: transparent; bottom: 0; border-radius: inherit; @@ -248,7 +187,7 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the width: 100%; } -.c18 { +.c39 { -webkit-align-items: center; -webkit-box-align: center; -ms-flex-align: center; @@ -283,34 +222,205 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the user-select: none; } -.c18:active .c20 { +.c39:active .c41 { background-color: #B8C0DC3d; } -.c18:focus .c20 { +.c39:focus .c41 { background-color: #B8C0DC3d; } -.c18:hover .c20 { +.c39:hover .c41 { background-color: #98A1C014; } -.c18:disabled { +.c39:disabled { cursor: default; opacity: 0.6; } -.c18:disabled:active .c20, -.c18:disabled:focus .c20, -.c18:disabled:hover .c20 { +.c39:disabled:active .c41, +.c39:disabled:focus .c41, +.c39:disabled:hover .c41 { background-color: transparent; } -.c4 { +.c54 { + color: #FF5F52; +} + +.c61 { + opacity: 0; + -webkit-transition: opacity 250ms ease-in; + transition: opacity 250ms ease-in; + width: 20px; + height: 20px; + border-radius: 50%; +} + +.c60 { + width: 20px; + height: 20px; + background: #22222212; + -webkit-transition: background-color 250ms ease-in; + transition: background-color 250ms ease-in; + box-shadow: 0 0 1px white; + border-radius: 50%; +} + +.c59 { + position: relative; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; +} + +.c67 { + color: #CECECE; + font-weight: 485; + font-size: 16px; +} + +.c18 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + height: 436px; + margin-bottom: 24px; + -webkit-align-items: flex-start; + -webkit-box-align: flex-start; + -ms-flex-align: flex-start; + align-items: flex-start; + width: 100%; +} + +.c24 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + -webkit-align-items: flex-end; + -webkit-box-align: flex-end; + -ms-flex-align: flex-end; + align-items: flex-end; + height: 100%; + margin-bottom: 44px; + padding-bottom: 66px; + overflow: hidden; +} + +.c20 { + height: 16px; + width: 180px; +} + +.c21 { + height: 32px; + border-radius: 8px; +} + +.c22 { + margin-top: 4px; + height: 40px; +} + +.c25 { + -webkit-animation: wave 8s cubic-bezier(0.36,0.45,0.63,0.53) infinite; + animation: wave 8s cubic-bezier(0.36,0.45,0.63,0.53) infinite; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + overflow: hidden; + margin-top: 90px; +} + +.c23 { + height: 6px; +} + +.c57 { + gap: 12px; + width: 100%; +} + +.c58 { + gap: 8px; + width: 100%; +} + +.c62 { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.c63 { + -webkit-flex-wrap: wrap; + -ms-flex-wrap: wrap; + flex-wrap: wrap; +} + +.c65 { + gap: 8px; + padding: 8px 12px; + border-radius: 20px; + color: #FC72FF; + background-color: #FC72FF1f; + font-size: 14px; + font-weight: 535; + line-height: 16px; + width: -webkit-max-content; + width: -moz-max-content; + width: max-content; + -webkit-text-decoration: none; + text-decoration: none; + cursor: pointer; + -webkit-transition-duration: 125ms; + transition-duration: 125ms; +} + +.c65:hover { + opacity: 0.6; +} + +.c65:active { + opacity: 0.4; +} + +.c66 { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 100%; + line-height: 24px; + white-space: pre-wrap; +} + +.c33 { + height: 16px; + width: 80px; +} + +.c36 { + height: 20px; + width: 20px; + border-radius: 100px; +} + +.c6 { gap: 36px; } -.c5 { +.c7 { -webkit-text-decoration: none; text-decoration: none; -webkit-text-decoration: none; @@ -320,21 +430,21 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the transition-duration: 125ms; } -.c5:hover { +.c7:hover { opacity: 0.6; } -.c5:active { +.c7:active { opacity: 0.4; } -.c14 { +.c16 { background: #F9F9F9; padding: 2px 6px; border-radius: 4px; } -.c15 { +.c17 { -webkit-text-decoration: none; text-decoration: none; cursor: pointer; @@ -342,21 +452,21 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the transition-duration: 125ms; } -.c15:hover { +.c17:hover { opacity: 0.6; } -.c15:active { +.c17:active { opacity: 0.4; } -.c11 { +.c13 { position: relative; top: 0; left: 0; } -.c12 { +.c14 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -367,39 +477,35 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the left: 0; } -.c12 img { +.c14 img { width: 16px; height: 32px; object-fit: cover; } -.c12 img:first-child { +.c14 img:first-child { border-radius: 16px 0 0 16px; object-position: 0 0; } -.c12 img:last-child { +.c14 img:last-child { border-radius: 0 16px 16px 0; object-position: 100% 0; } -.c13 { +.c15 { width: 32px; height: 32px; border-radius: 50%; } -.c33 { - color: #FF5F52; -} - -.c23 { +.c44 { font-weight: 485; font-size: 24px; line-height: 36px; } -.c22 { +.c43 { gap: 24px; padding: 20px; border-radius: 20px; @@ -407,7 +513,7 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the width: 100%; } -.c24 { +.c45 { gap: 8px; -webkit-flex: 1; -ms-flex: 1; @@ -415,14 +521,14 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the min-width: 180px; } -.c25 { +.c46 { -webkit-box-pack: justify; -webkit-justify-content: space-between; -ms-flex-pack: justify; justify-content: space-between; } -.c26 { +.c47 { font-weight: 485; font-size: 18px; line-height: 24px; @@ -431,7 +537,7 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the width: max-content; } -.c27 { +.c48 { height: 8px; width: 40.698463777008904%; background: #FC72FF; @@ -440,7 +546,7 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the border-right: 1px solid #F9F9F9; } -.c28 { +.c49 { height: 8px; width: 59.3015362229911%; background: #4C82FB; @@ -449,7 +555,7 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the border-left: 1px solid #F9F9F9; } -.c29 { +.c50 { gap: 4px; width: 100%; -webkit-align-items: flex-end; @@ -458,23 +564,143 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the align-items: flex-end; } -.c30 { +.c51 { color: #222222; font-size: 36px; font-weight: 485; line-height: 44px; } -.c17 { +.c38 { gap: 12px; } -.c19 { +.c40 { padding: 12px 16px 12px 12px; border-radius: 900px; width: 50%; } +.c28 { + gap: 24px; + border-radius: 20px; + border: 1px solid #22222212; + padding-bottom: 12px; + overflow-y: hidden; + -webkit-scrollbar-width: thin; + -moz-scrollbar-width: thin; + -ms-scrollbar-width: thin; + scrollbar-width: thin; + -webkit-scrollbar-color: #22222212 transparent; + -moz-scrollbar-color: #22222212 transparent; + -ms-scrollbar-color: #22222212 transparent; + scrollbar-color: #22222212 transparent; + height: 100%; +} + +.c28::-webkit-scrollbar { + background: transparent; + height: 4px; + overflow-x: scroll; +} + +.c28::-webkit-scrollbar-thumb { + background: #22222212; + border-radius: 8px; +} + +.c29 { + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + -ms-flex-pack: justify; + justify-content: space-between; + border-bottom: 1px solid #22222212; + padding: 12px; + min-width: -webkit-max-content; + min-width: -moz-max-content; + min-width: max-content; +} + +.c35 { + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + -ms-flex-pack: justify; + justify-content: space-between; + border-bottom: none; + padding: 12px; + min-width: -webkit-max-content; + min-width: -moz-max-content; + min-width: max-content; +} + +.c30 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + padding: 0px 8px; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; + width: auto; + min-width: 136px !important; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; +} + +.c31 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + padding: 0px 8px; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; + width: auto; + min-width: 121px !important; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; +} + +.c32 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + padding: 0px 8px; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; + width: auto; + min-width: 121px !important; + -webkit-box-pack: end; + -webkit-justify-content: flex-end; + -ms-flex-pack: end; + justify-content: flex-end; +} + +.c34 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + padding: 0px 8px; + -webkit-flex: unset; + -ms-flex: unset; + flex: unset; + width: 44px; + min-width: unset !important; + -webkit-box-pack: end; + -webkit-justify-content: flex-end; + -ms-flex-pack: end; + justify-content: flex-end; +} + .c2 { padding: 48px; width: 100%; @@ -482,21 +708,43 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the -webkit-box-align: flex-start; -ms-flex-align: flex-start; align-items: flex-start; + gap: 60px; } -.c16 { +.c4 { + gap: 24px; + width: 65vw; + overflow: hidden; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; +} + +.c26 { + border: 0.5px solid #22222212; + margin: 16px 0px; + width: 100%; +} + +.c27 { + width: 180px; + height: 32px; +} + +.c37 { gap: 24px; margin: 0 48px 0 auto; width: 22vw; min-width: 360px; } -.c34 { +.c55 { gap: 24px; padding: 20px; } -.c35 { +.c56 { width: 100%; font-size: 24px; font-weight: 485; @@ -504,19 +752,19 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the } @media (max-width:1023px) and (min-width:640px) { - .c36 { + .c57 { max-width: 45%; } } @media (max-width:1023px) { - .c23 { + .c44 { width: 100%; } } @media (max-width:1023px) { - .c22 { + .c43 { -webkit-flex-direction: row; -ms-flex-direction: row; flex-direction: row; @@ -533,13 +781,13 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the } @media (max-width:640px) { - .c24 { + .c45 { min-width: 150px; } } @media (max-width:1023px) { - .c25 { + .c46 { -webkit-flex-direction: column; -ms-flex-direction: column; flex-direction: column; @@ -547,7 +795,7 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the } @media (max-width:1023px) { - .c26 { + .c47 { font-size: 20px; line-height: 28px; width: 100%; @@ -555,7 +803,7 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the } @media (max-width:1023px) { - .c29 { + .c50 { -webkit-flex-direction: column; -ms-flex-direction: column; flex-direction: column; @@ -568,14 +816,14 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the } @media (max-width:1023px) { - .c30 { + .c51 { font-size: 20px; line-height: 28px; } } @media (max-width:1023px) { - .c17 { + .c38 { display: none; } } @@ -585,6 +833,7 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the -webkit-flex-direction: column; -ms-flex-direction: column; flex-direction: column; + gap: unset; } } @@ -595,7 +844,13 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the } @media (max-width:1023px) { - .c16 { + .c4 { + width: 100%; + } +} + +@media (max-width:1023px) { + .c37 { margin: 44px 0px; width: 100%; min-width: unset; @@ -603,7 +858,7 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the } @media (max-width:1023px) and (min-width:640px) { - .c34 { + .c55 { -webkit-flex-direction: row; -ms-flex-direction: row; flex-direction: row; @@ -615,7 +870,7 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the } @media (max-width:639px) { - .c34 { + .c55 { padding: unset; } } @@ -627,147 +882,847 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the class="c3 c4" >
- -
- Explore -
-
-
-  >  -
- -
- Pool -
-
-
-  >  -
-
- USDC / WETH (0x88e6...5640) -
-
-
-
- - +
+ Explore +
+ +
+  >  +
+ +
+ Pool +
+
+
+  >  +
+
+ USDC / WETH (0x88e6...5640)
- USDC / WETH +
+
+
+ + +
+
+
+ USDC / WETH +
+
+
+ 0.05% +
+ + +
- 0.05% -
- - +
+
+
- +
+
+
+ + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+ + + + + Time +
+
+
+ Type +
+
+ USD +
+
+
+
+
+
+
+
+ Maker +
+
+ Txn +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Stats
Pool balances
90.93M USDC
82,526.49 WETH
@@ -777,36 +1732,36 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the data-testid="pool-balance-chart" >
TVL
$223.2M
0.37%
@@ -827,28 +1782,28 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the
24H volume
$233.4M
17.75%
@@ -869,18 +1824,18 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the
24H fees
$116.7K
@@ -888,56 +1843,56 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the
Info
UNKNOWN logo
Unknown Token
UNKNOWN
WETH logo
Wrapped Ether
WETH
No token information available diff --git a/src/pages/PoolDetails/index.test.tsx b/src/pages/PoolDetails/index.test.tsx index ce6b343c02..093dd1b781 100644 --- a/src/pages/PoolDetails/index.test.tsx +++ b/src/pages/PoolDetails/index.test.tsx @@ -76,7 +76,6 @@ describe('PoolDetailsPage', () => { }) }) - // TODO replace with loading skeleton when designed it('nothing displayed while data is loading', () => { mocked(usePoolData).mockReturnValue({ data: undefined, @@ -86,7 +85,7 @@ describe('PoolDetailsPage', () => { render() waitFor(() => { - expect(screen.getByText(/not found/i)).not.toBeInTheDocument() + expect(screen.getByTestId('pdp-links-loading-skeleton')).toBeInTheDocument() }) }) diff --git a/src/pages/PoolDetails/index.tsx b/src/pages/PoolDetails/index.tsx index 971e42be17..e5420f2c1e 100644 --- a/src/pages/PoolDetails/index.tsx +++ b/src/pages/PoolDetails/index.tsx @@ -1,6 +1,8 @@ import { Trans } from '@lingui/macro' import Column from 'components/Column' import Row from 'components/Row' +import { LoadingBubble } from 'components/Tokens/loading' +import { LoadingChart } from 'components/Tokens/TokenDetails/Skeleton' import { TokenDescription } from 'components/Tokens/TokenDetails/TokenDescription' import { getValidUrlChainName, supportedChainIdFromGQLChain } from 'graphql/data/util' import { usePoolData } from 'graphql/thegraph/PoolData' @@ -15,14 +17,18 @@ import { isAddress } from 'utils' import { PoolDetailsHeader } from './PoolDetailsHeader' import { PoolDetailsStats } from './PoolDetailsStats' import { PoolDetailsStatsButtons } from './PoolDetailsStatsButtons' +import { PoolDetailsTableSkeleton } from './PoolDetailsTableSkeleton' +import { DetailBubble, SmallDetailBubble } from './shared' const PageWrapper = styled(Row)` padding: 48px; width: 100%; align-items: flex-start; + gap: 60px; @media (max-width: ${BREAKPOINTS.lg - 1}px) { flex-direction: column; + gap: unset; } @media (max-width: ${BREAKPOINTS.sm - 1}px) { @@ -30,6 +36,33 @@ const PageWrapper = styled(Row)` } ` +const LeftColumn = styled(Column)` + gap: 24px; + width: 65vw; + overflow: hidden; + justify-content: flex-start; + + @media (max-width: ${BREAKPOINTS.lg - 1}px) { + width: 100%; + } +` + +const HR = styled.hr` + border: 0.5px solid ${({ theme }) => theme.surface3}; + margin: 16px 0px; + width: 100%; +` + +const ChartHeaderBubble = styled(LoadingBubble)` + width: 180px; + height: 32px; +` + +const LinkColumn = styled(Column)` + gap: 16px; + padding: 20px; +` + const RightColumn = styled(Column)` gap: 24px; margin: 0 48px 0 auto; @@ -79,31 +112,55 @@ export default function PoolDetailsPage() { const isInvalidPool = !chainName || !poolAddress || !getValidUrlChainName(chainName) || !isAddress(poolAddress) const poolNotFound = (!loading && !poolData) || isInvalidPool - // TODO(WEB-2814): Add skeleton once designed - if (loading) return null if (poolNotFound) return return ( - + + + + + +
+ + +
- - {poolData && } - {(token0 || token1) && ( - - - Info - - {token0 && } - {token1 && } - - )} + + + {(token0 || token1 || loading) && + (loading ? ( + + + {Array.from({ length: 3 }).map((_, i) => ( + + + + + ))} + + ) : ( + + + Info + + {token0 && } + {token1 && } + + ))}
) diff --git a/src/pages/PoolDetails/shared.ts b/src/pages/PoolDetails/shared.ts new file mode 100644 index 0000000000..a787f33a96 --- /dev/null +++ b/src/pages/PoolDetails/shared.ts @@ -0,0 +1,13 @@ +import { LoadingBubble } from 'components/Tokens/loading' +import styled from 'styled-components' + +export const DetailBubble = styled(LoadingBubble)<{ $height?: number; $width?: number }>` + height: ${({ $height }) => ($height ? `${$height}px` : '16px')}; + width: ${({ $width }) => ($width ? `${$width}px` : '80px')}; +` + +export const SmallDetailBubble = styled(LoadingBubble)` + height: 20px; + width: 20px; + border-radius: 100px; +` diff --git a/src/pages/RouteDefinitions.tsx b/src/pages/RouteDefinitions.tsx index fa713a3b35..0ff735ad11 100644 --- a/src/pages/RouteDefinitions.tsx +++ b/src/pages/RouteDefinitions.tsx @@ -139,7 +139,11 @@ export const routes: RouteDefinition[] = [ }), createRouteDefinition({ path: 'explore/pools/:chainName/:poolAddress', - getElement: () => , + getElement: () => ( + + + + ), enabled: (args) => Boolean(args.infoExplorePageEnabled && args.infoPoolPageEnabled), }), createRouteDefinition({