refactor: consolidate price delta components (#7268)

* refactor: move delta component out of price chart file

* refactor: remove PortfolioArrow

* refactor: search row arrow cell

* fix: svg prop + snapshots failing unit tests

* refactor: css nit
This commit is contained in:
cartcrom 2023-09-08 14:58:39 -04:00 committed by GitHub
parent f69226c1d1
commit bb28235bee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 164 additions and 223 deletions

@ -5,13 +5,11 @@ import { useWeb3React } from '@web3-react/core'
import { sendAnalyticsEvent, TraceEvent } from 'analytics' import { sendAnalyticsEvent, TraceEvent } from 'analytics'
import { ButtonEmphasis, ButtonSize, LoadingButtonSpinner, ThemeButton } from 'components/Button' import { ButtonEmphasis, ButtonSize, LoadingButtonSpinner, ThemeButton } from 'components/Button'
import Column from 'components/Column' import Column from 'components/Column'
import { ArrowChangeDown } from 'components/Icons/ArrowChangeDown'
import { ArrowChangeUp } from 'components/Icons/ArrowChangeUp'
import { Power } from 'components/Icons/Power' import { Power } from 'components/Icons/Power'
import { Settings } from 'components/Icons/Settings' import { Settings } from 'components/Icons/Settings'
import { AutoRow } from 'components/Row' import { AutoRow } from 'components/Row'
import { LoadingBubble } from 'components/Tokens/loading' import { LoadingBubble } from 'components/Tokens/loading'
import { formatDelta } from 'components/Tokens/TokenDetails/PriceChart' import { DeltaArrow, formatDelta } from 'components/Tokens/TokenDetails/Delta'
import Tooltip from 'components/Tooltip' import Tooltip from 'components/Tooltip'
import { getConnection } from 'connection' import { getConnection } from 'connection'
import { useDisableNFTRoutes } from 'hooks/useDisableNFTRoutes' import { useDisableNFTRoutes } from 'hooks/useDisableNFTRoutes'
@ -20,11 +18,11 @@ import { useProfilePageState, useSellAsset, useWalletCollections } from 'nft/hoo
import { useIsNftClaimAvailable } from 'nft/hooks/useIsNftClaimAvailable' import { useIsNftClaimAvailable } from 'nft/hooks/useIsNftClaimAvailable'
import { ProfilePageStateType } from 'nft/types' import { ProfilePageStateType } from 'nft/types'
import { useCallback, useState } from 'react' import { useCallback, useState } from 'react'
import { CreditCard, IconProps, Info } from 'react-feather' import { CreditCard, Info } from 'react-feather'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { useAppDispatch } from 'state/hooks' import { useAppDispatch } from 'state/hooks'
import { updateSelectedWallet } from 'state/user/reducer' import { updateSelectedWallet } from 'state/user/reducer'
import styled, { useTheme } from 'styled-components' import styled from 'styled-components'
import { CopyHelper, ExternalLink, ThemedText } from 'theme' import { CopyHelper, ExternalLink, ThemedText } from 'theme'
import { shortenAddress } from 'utils' import { shortenAddress } from 'utils'
import { formatNumber, NumberType } from 'utils/formatNumbers' import { formatNumber, NumberType } from 'utils/formatNumbers'
@ -151,15 +149,6 @@ const PortfolioDrawerContainer = styled(Column)`
flex: 1; flex: 1;
` `
export function PortfolioArrow({ change, ...rest }: { change: number } & IconProps) {
const theme = useTheme()
return change < 0 ? (
<ArrowChangeDown color={theme.critical} width={16} {...rest} />
) : (
<ArrowChangeUp color={theme.success} width={16} {...rest} />
)
}
export default function AuthenticatedHeader({ account, openSettings }: { account: string; openSettings: () => void }) { export default function AuthenticatedHeader({ account, openSettings }: { account: string; openSettings: () => void }) {
const { connector } = useWeb3React() const { connector } = useWeb3React()
const { ENSName } = useENSName(account) const { ENSName } = useENSName(account)
@ -287,7 +276,7 @@ export default function AuthenticatedHeader({ account, openSettings }: { account
<AutoRow marginBottom="20px"> <AutoRow marginBottom="20px">
{absoluteChange !== 0 && percentChange && ( {absoluteChange !== 0 && percentChange && (
<> <>
<PortfolioArrow change={absoluteChange as number} /> <DeltaArrow delta={absoluteChange} />
<ThemedText.BodySecondary> <ThemedText.BodySecondary>
{`${formatNumber({ {`${formatNumber({
input: Math.abs(absoluteChange as number), input: Math.abs(absoluteChange as number),

@ -2,7 +2,7 @@ import { BrowserEvent, InterfaceElementName, SharedEventName } from '@uniswap/an
import { TraceEvent } from 'analytics' import { TraceEvent } from 'analytics'
import { useCachedPortfolioBalancesQuery } from 'components/AccountDrawer/PrefetchBalancesWrapper' import { useCachedPortfolioBalancesQuery } from 'components/AccountDrawer/PrefetchBalancesWrapper'
import Row from 'components/Row' import Row from 'components/Row'
import { formatDelta } from 'components/Tokens/TokenDetails/PriceChart' import { DeltaArrow, formatDelta } from 'components/Tokens/TokenDetails/Delta'
import { TokenBalance } from 'graphql/data/__generated__/types-and-hooks' import { TokenBalance } from 'graphql/data/__generated__/types-and-hooks'
import { getTokenDetailsURL, gqlToCurrency, logSentryErrorForUnsupportedChain } from 'graphql/data/util' import { getTokenDetailsURL, gqlToCurrency, logSentryErrorForUnsupportedChain } from 'graphql/data/util'
import { useAtomValue } from 'jotai/utils' import { useAtomValue } from 'jotai/utils'
@ -15,7 +15,6 @@ import { formatNumber, NumberType } from 'utils/formatNumbers'
import { splitHiddenTokens } from 'utils/splitHiddenTokens' import { splitHiddenTokens } from 'utils/splitHiddenTokens'
import { useToggleAccountDrawer } from '../..' import { useToggleAccountDrawer } from '../..'
import { PortfolioArrow } from '../../AuthenticatedHeader'
import { hideSmallBalancesAtom } from '../../SmallBalanceToggle' import { hideSmallBalancesAtom } from '../../SmallBalanceToggle'
import { ExpandoRow } from '../ExpandoRow' import { ExpandoRow } from '../ExpandoRow'
import { PortfolioLogo } from '../PortfolioLogo' import { PortfolioLogo } from '../PortfolioLogo'
@ -115,7 +114,7 @@ function TokenRow({ token, quantity, denominatedValue, tokenProjectMarket }: Tok
})} })}
</ThemedText.SubHeader> </ThemedText.SubHeader>
<Row justify="flex-end"> <Row justify="flex-end">
<PortfolioArrow change={percentChange} size={20} strokeWidth={1.75} /> <DeltaArrow delta={percentChange} />
<ThemedText.BodySecondary>{formatDelta(percentChange)}</ThemedText.BodySecondary> <ThemedText.BodySecondary>{formatDelta(percentChange)}</ThemedText.BodySecondary>
</Row> </Row>
</> </>

@ -20,18 +20,15 @@ import styled from 'styled-components'
import { ThemedText } from 'theme' import { ThemedText } from 'theme'
import { formatUSDPrice } from 'utils/formatNumbers' import { formatUSDPrice } from 'utils/formatNumbers'
import { DeltaText, getDeltaArrow } from '../Tokens/TokenDetails/PriceChart' import { DeltaArrow, DeltaText } from '../Tokens/TokenDetails/Delta'
import { useAddRecentlySearchedAsset } from './RecentlySearchedAssets' import { useAddRecentlySearchedAsset } from './RecentlySearchedAssets'
import * as styles from './SearchBar.css' import * as styles from './SearchBar.css'
const PriceChangeContainer = styled.div` const PriceChangeContainer = styled.div`
display: flex; display: flex;
align-items: center; align-items: center;
`
const ArrowCell = styled.span`
padding-top: 4px; padding-top: 4px;
padding-right: 2px; gap: 2px;
` `
interface CollectionRowProps { interface CollectionRowProps {
@ -156,8 +153,6 @@ export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index,
} }
}, [toggleOpen, isHovered, token, navigate, handleClick, tokenDetailsPath]) }, [toggleOpen, isHovered, token, navigate, handleClick, tokenDetailsPath])
const arrow = getDeltaArrow(token.market?.pricePercentChange?.value, 16)
return ( return (
<Link <Link
data-testid={`searchbar-token-row-${token.chain}-${token.address ?? 'NATIVE'}`} data-testid={`searchbar-token-row-${token.chain}-${token.address ?? 'NATIVE'}`}
@ -192,7 +187,7 @@ export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index,
<Box className={styles.primaryText}>{formatUSDPrice(token.market.price.value)}</Box> <Box className={styles.primaryText}>{formatUSDPrice(token.market.price.value)}</Box>
</Row> </Row>
<PriceChangeContainer> <PriceChangeContainer>
<ArrowCell>{arrow}</ArrowCell> <DeltaArrow delta={token.market?.pricePercentChange?.value} />
<ThemedText.BodySmall> <ThemedText.BodySmall>
<DeltaText delta={token.market?.pricePercentChange?.value}> <DeltaText delta={token.market?.pricePercentChange?.value}>
{Math.abs(token.market?.pricePercentChange?.value ?? 0).toFixed(2)}% {Math.abs(token.market?.pricePercentChange?.value ?? 0).toFixed(2)}%

@ -0,0 +1,47 @@
import { ArrowChangeDown } from 'components/Icons/ArrowChangeDown'
import { ArrowChangeUp } from 'components/Icons/ArrowChangeUp'
import styled from 'styled-components'
const StyledUpArrow = styled(ArrowChangeUp)<{ $noColor?: boolean }>`
color: ${({ theme, $noColor }) => ($noColor ? theme.neutral2 : theme.success)};
`
const StyledDownArrow = styled(ArrowChangeDown)<{ $noColor?: boolean }>`
color: ${({ theme, $noColor }) => ($noColor ? theme.neutral2 : theme.critical)};
`
export function calculateDelta(start: number, current: number) {
return (current / start - 1) * 100
}
function isValidDelta(delta: number | null | undefined): delta is number {
// Null-check not including zero
return delta !== null && delta !== undefined && delta !== Infinity && !isNaN(delta)
}
interface DeltaArrowProps {
delta?: number | null
noColor?: boolean
size?: number
}
export function formatDelta(delta: number | null | undefined) {
if (!isValidDelta(delta)) return '-'
const formattedDelta = Math.abs(delta).toFixed(2) + '%'
return formattedDelta
}
export function DeltaArrow({ delta, noColor = false, size = 16 }: DeltaArrowProps) {
if (!isValidDelta(delta)) return null
return Math.sign(delta) < 0 ? (
<StyledDownArrow width={size} height={size} key="arrow-down" aria-label="down" $noColor={noColor} />
) : (
<StyledUpArrow width={size} height={size} key="arrow-up" aria-label="up" $noColor={noColor} />
)
}
export const DeltaText = styled.span<{ delta?: number }>`
color: ${({ theme, delta }) =>
delta !== undefined ? (Math.sign(delta) < 0 ? theme.critical : theme.success) : theme.neutral1};
`

@ -6,14 +6,12 @@ import { GlyphCircle } from '@visx/glyph'
import { Line } from '@visx/shape' import { Line } from '@visx/shape'
import AnimatedInLineChart from 'components/Charts/AnimatedInLineChart' import AnimatedInLineChart from 'components/Charts/AnimatedInLineChart'
import FadedInLineChart from 'components/Charts/FadeInLineChart' import FadedInLineChart from 'components/Charts/FadeInLineChart'
import { ArrowChangeDown } from 'components/Icons/ArrowChangeDown'
import { ArrowChangeUp } from 'components/Icons/ArrowChangeUp'
import { MouseoverTooltip } from 'components/Tooltip' import { MouseoverTooltip } from 'components/Tooltip'
import { bisect, curveCardinal, NumberValue, scaleLinear, timeDay, timeHour, timeMinute, timeMonth } from 'd3' import { bisect, curveCardinal, NumberValue, scaleLinear, timeDay, timeHour, timeMinute, timeMonth } from 'd3'
import { PricePoint, TimePeriod } from 'graphql/data/util' import { PricePoint, TimePeriod } from 'graphql/data/util'
import { useActiveLocale } from 'hooks/useActiveLocale' import { useActiveLocale } from 'hooks/useActiveLocale'
import { ReactNode, useCallback, useEffect, useMemo, useState } from 'react' import { ReactNode, useCallback, useEffect, useMemo, useState } from 'react'
import { ArrowDownRight, ArrowUpRight, Info, TrendingUp } from 'react-feather' import { Info, TrendingUp } from 'react-feather'
import styled, { useTheme } from 'styled-components' import styled, { useTheme } from 'styled-components'
import { ThemedText } from 'theme' import { ThemedText } from 'theme'
import { textFadeIn } from 'theme/styles' import { textFadeIn } from 'theme/styles'
@ -27,6 +25,8 @@ import {
} from 'utils/formatChartTimes' } from 'utils/formatChartTimes'
import { formatUSDPrice } from 'utils/formatNumbers' import { formatUSDPrice } from 'utils/formatNumbers'
import { calculateDelta, DeltaArrow, formatDelta } from './Delta'
const DATA_EMPTY = { value: 0, timestamp: 0 } const DATA_EMPTY = { value: 0, timestamp: 0 }
export function getPriceBounds(pricePoints: PricePoint[]): [number, number] { export function getPriceBounds(pricePoints: PricePoint[]): [number, number] {
@ -36,56 +36,6 @@ export function getPriceBounds(pricePoints: PricePoint[]): [number, number] {
return [min, max] return [min, max]
} }
const StyledUpArrow = styled(ArrowChangeUp)`
color: ${({ theme }) => theme.success};
`
const StyledDownArrow = styled(ArrowChangeDown)`
color: ${({ theme }) => theme.critical};
`
const DefaultUpArrow = styled(ArrowUpRight)`
color: ${({ theme }) => theme.neutral3};
`
const DefaultDownArrow = styled(ArrowDownRight)`
color: ${({ theme }) => theme.neutral3};
`
function calculateDelta(start: number, current: number) {
return (current / start - 1) * 100
}
export function getDeltaArrow(delta: number | null | undefined, iconSize = 16, styled = true) {
// Null-check not including zero
if (delta === null || delta === undefined) {
return null
} else if (Math.sign(delta) < 0) {
return styled ? (
<StyledDownArrow width={iconSize} height={iconSize} key="arrow-down" aria-label="down" />
) : (
<DefaultDownArrow size={iconSize} key="arrow-down" aria-label="down" />
)
}
return styled ? (
<StyledUpArrow width={iconSize} height={iconSize} key="arrow-up" aria-label="up" />
) : (
<DefaultUpArrow size={iconSize} key="arrow-up" aria-label="up" />
)
}
export function formatDelta(delta: number | null | undefined) {
// Null-check not including zero
if (delta === null || delta === undefined || delta === Infinity || isNaN(delta)) {
return '-'
}
const formattedDelta = Math.abs(delta).toFixed(2) + '%'
return formattedDelta
}
export const DeltaText = styled.span<{ delta?: number }>`
color: ${({ theme, delta }) =>
delta !== undefined ? (Math.sign(delta) < 0 ? theme.critical : theme.success) : theme.neutral1};
`
const ChartHeader = styled.div` const ChartHeader = styled.div`
position: absolute; position: absolute;
${textFadeIn}; ${textFadeIn};
@ -113,10 +63,6 @@ const DeltaContainer = styled.div`
margin-top: 4px; margin-top: 4px;
color: ${({ theme }) => theme.neutral2}; color: ${({ theme }) => theme.neutral2};
` `
export const ArrowCell = styled.div`
padding-right: 3px;
display: flex;
`
const OutdatedPriceContainer = styled.div` const OutdatedPriceContainer = styled.div`
display: flex; display: flex;
@ -151,6 +97,22 @@ function fixChart(prices: PricePoint[] | undefined | null) {
const margin = { top: 100, bottom: 48, crosshair: 72 } const margin = { top: 100, bottom: 48, crosshair: 72 }
const timeOptionsHeight = 44 const timeOptionsHeight = 44
interface ChartDeltaProps {
startingPrice: PricePoint
endingPrice: PricePoint
noColor?: boolean
}
function ChartDelta({ startingPrice, endingPrice, noColor }: ChartDeltaProps) {
const delta = calculateDelta(startingPrice.value, endingPrice.value)
return (
<DeltaContainer>
{formatDelta(delta)}
<DeltaArrow delta={delta} noColor={noColor} />
</DeltaContainer>
)
}
interface PriceChartProps { interface PriceChartProps {
width: number width: number
height: number height: number
@ -202,10 +164,6 @@ export function PriceChart({ width, height, prices: originalPrices, timePeriod }
return DATA_EMPTY return DATA_EMPTY
}, [prices]) }, [prices])
const totalDelta = calculateDelta(firstPrice.value, lastPrice.value)
const formattedTotalDelta = formatDelta(totalDelta)
const defaultArrow = getDeltaArrow(totalDelta, 20, false)
// first price point on the x-axis of the current time period's chart // first price point on the x-axis of the current time period's chart
const startingPrice = originalPrices?.[0] ?? DATA_EMPTY const startingPrice = originalPrices?.[0] ?? DATA_EMPTY
// last price point on the x-axis of the current time period's chart // last price point on the x-axis of the current time period's chart
@ -334,9 +292,6 @@ export function PriceChart({ width, height, prices: originalPrices, timePeriod }
} }
const updatedTicks = maxTicks > 0 ? (ticks.length > maxTicks ? calculateTicks(ticks) : ticks) : [] const updatedTicks = maxTicks > 0 ? (ticks.length > maxTicks ? calculateTicks(ticks) : ticks) : []
const delta = calculateDelta(startingPrice.value, displayPrice.value)
const formattedDelta = formatDelta(delta)
const arrow = getDeltaArrow(delta)
const crosshairEdgeMax = width * 0.85 const crosshairEdgeMax = width * 0.85
const crosshairAtEdge = !!crosshair && crosshair > crosshairEdgeMax const crosshairAtEdge = !!crosshair && crosshair > crosshairEdgeMax
@ -355,10 +310,7 @@ export function PriceChart({ width, height, prices: originalPrices, timePeriod }
{displayPrice.value ? ( {displayPrice.value ? (
<> <>
<TokenPrice>{formatUSDPrice(displayPrice.value)}</TokenPrice> <TokenPrice>{formatUSDPrice(displayPrice.value)}</TokenPrice>
<DeltaContainer> <ChartDelta startingPrice={startingPrice} endingPrice={displayPrice} />
{formattedDelta}
<ArrowCell>{arrow}</ArrowCell>
</DeltaContainer>
</> </>
) : lastPrice.value ? ( ) : lastPrice.value ? (
<OutdatedContainer> <OutdatedContainer>
@ -368,10 +320,7 @@ export function PriceChart({ width, height, prices: originalPrices, timePeriod }
<Info size={16} /> <Info size={16} />
</MouseoverTooltip> </MouseoverTooltip>
</OutdatedPriceContainer> </OutdatedPriceContainer>
<DeltaContainer> <ChartDelta startingPrice={firstPrice} endingPrice={lastPrice} noColor />
{formattedTotalDelta}
<ArrowCell>{defaultArrow}</ArrowCell>
</DeltaContainer>
</OutdatedContainer> </OutdatedContainer>
) : ( ) : (
<> <>

@ -2,7 +2,7 @@
exports[`PriceChart renders correctly with all prices filled 1`] = ` exports[`PriceChart renders correctly with all prices filled 1`] = `
<DocumentFragment> <DocumentFragment>
.c4 { .c3 {
color: #40B66B; color: #40B66B;
} }
@ -34,14 +34,6 @@ exports[`PriceChart renders correctly with all prices filled 1`] = `
color: #7D7D7D; color: #7D7D7D;
} }
.c3 {
padding-right: 3px;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
}
<div <div
class="c0" class="c0"
data-cy="chart-header" data-cy="chart-header"
@ -55,12 +47,9 @@ exports[`PriceChart renders correctly with all prices filled 1`] = `
class="c2" class="c2"
> >
0.00% 0.00%
<div
class="c3"
>
<svg <svg
aria-label="up" aria-label="up"
class="c4" class="c3"
fill="none" fill="none"
height="16" height="16"
viewBox="0 0 24 24" viewBox="0 0 24 24"
@ -74,7 +63,6 @@ exports[`PriceChart renders correctly with all prices filled 1`] = `
</svg> </svg>
</div> </div>
</div> </div>
</div>
<svg <svg
data-cy="price-chart" data-cy="price-chart"
height="392" height="392"
@ -426,8 +414,8 @@ exports[`PriceChart renders correctly with some prices filled 1`] = `
height: inherit; height: inherit;
} }
.c7 { .c6 {
color: #CECECE; color: #7D7D7D;
} }
.c0 { .c0 {
@ -462,14 +450,6 @@ exports[`PriceChart renders correctly with some prices filled 1`] = `
color: #7D7D7D; color: #7D7D7D;
} }
.c6 {
padding-right: 3px;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
}
.c2 { .c2 {
display: -webkit-box; display: -webkit-box;
display: -webkit-flex; display: -webkit-flex;
@ -535,36 +515,23 @@ exports[`PriceChart renders correctly with some prices filled 1`] = `
class="c5" class="c5"
> >
0.00% 0.00%
<div
class="c6"
>
<svg <svg
aria-label="up" aria-label="up"
class="c7" class="c6"
fill="none" fill="none"
height="20" height="16"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24" viewBox="0 0 24 24"
width="20" width="16"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<line <path
x1="7" d="M13.3021 7.7547L17.6821 14.2475C18.4182 15.3388 17.7942 17 16.6482 17L7.3518 17C6.2058 17 5.5818 15.3376 6.3179 14.2475L10.6979 7.7547C11.377 6.7484 12.623 6.7484 13.3021 7.7547Z"
x2="17" fill="currentColor"
y1="17"
y2="7"
/>
<polyline
points="7 7 17 7 17 17"
/> />
</svg> </svg>
</div> </div>
</div> </div>
</div> </div>
</div>
<svg <svg
data-cy="price-chart" data-cy="price-chart"
height="392" height="392"

@ -33,7 +33,7 @@ import {
TokenSortMethod, TokenSortMethod,
useSetSortMethod, useSetSortMethod,
} from '../state' } from '../state'
import { ArrowCell, DeltaText, formatDelta, getDeltaArrow } from '../TokenDetails/PriceChart' import { DeltaArrow, DeltaText, formatDelta } from '../TokenDetails/Delta'
const Cell = styled.div` const Cell = styled.div`
display: flex; display: flex;
@ -103,8 +103,9 @@ const StyledTokenRow = styled.div<{
} }
` `
const ClickableContent = styled.div` const ClickableContent = styled.div<{ gap?: number }>`
display: flex; display: flex;
${({ gap }) => gap && `gap: ${gap}px`};
text-decoration: none; text-decoration: none;
color: ${({ theme }) => theme.neutral1}; color: ${({ theme }) => theme.neutral1};
align-items: center; align-items: center;
@ -184,6 +185,7 @@ const PercentChangeInfoCell = styled(Cell)`
@media only screen and (max-width: ${SMALL_MEDIA_BREAKPOINT}) { @media only screen and (max-width: ${SMALL_MEDIA_BREAKPOINT}) {
display: flex; display: flex;
gap: 3px;
justify-content: flex-end; justify-content: flex-end;
color: ${({ theme }) => theme.neutral2}; color: ${({ theme }) => theme.neutral2};
font-size: 12px; font-size: 12px;
@ -445,8 +447,6 @@ export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef<HT
const chainId = supportedChainIdFromGQLChain(filterNetwork) const chainId = supportedChainIdFromGQLChain(filterNetwork)
const timePeriod = useAtomValue(filterTimeAtom) const timePeriod = useAtomValue(filterTimeAtom)
const delta = token.market?.pricePercentChange?.value const delta = token.market?.pricePercentChange?.value
const arrow = getDeltaArrow(delta)
const smallArrow = getDeltaArrow(delta, 14)
const formattedDelta = formatDelta(delta) const formattedDelta = formatDelta(delta)
const exploreTokenSelectedEventProperties = { const exploreTokenSelectedEventProperties = {
@ -489,15 +489,15 @@ export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef<HT
<PriceInfoCell> <PriceInfoCell>
{price} {price}
<PercentChangeInfoCell> <PercentChangeInfoCell>
<ArrowCell>{smallArrow}</ArrowCell> <DeltaArrow delta={delta} size={14} />
<DeltaText delta={delta}>{formattedDelta}</DeltaText> <DeltaText delta={delta}>{formattedDelta}</DeltaText>
</PercentChangeInfoCell> </PercentChangeInfoCell>
</PriceInfoCell> </PriceInfoCell>
</ClickableContent> </ClickableContent>
} }
percentChange={ percentChange={
<ClickableContent> <ClickableContent gap={3}>
<ArrowCell>{arrow}</ArrowCell> <DeltaArrow delta={delta} />
<DeltaText delta={delta}>{formattedDelta}</DeltaText> <DeltaText delta={delta}>{formattedDelta}</DeltaText>
</ClickableContent> </ClickableContent>
} }

@ -2,22 +2,14 @@
exports[`LoadedRow.tsx renders a row 1`] = ` exports[`LoadedRow.tsx renders a row 1`] = `
<DocumentFragment> <DocumentFragment>
.c19 { .c18 {
color: #40B66B; color: #40B66B;
} }
.c20 { .c19 {
color: #40B66B; color: #40B66B;
} }
.c18 {
padding-right: 3px;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
}
.c9 { .c9 {
opacity: 0; opacity: 0;
-webkit-transition: opacity 250ms ease-in; -webkit-transition: opacity 250ms ease-in;
@ -113,6 +105,22 @@ exports[`LoadedRow.tsx renders a row 1`] = `
cursor: pointer; cursor: pointer;
} }
.c21 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
gap: 3px;
-webkit-text-decoration: none;
text-decoration: none;
color: #222222;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
cursor: pointer;
}
.c6 { .c6 {
gap: 8px; gap: 8px;
max-width: 100%; max-width: 100%;
@ -156,7 +164,7 @@ exports[`LoadedRow.tsx renders a row 1`] = `
padding-right: 8px; padding-right: 8px;
} }
.c21 { .c20 {
padding-right: 8px; padding-right: 8px;
} }
@ -264,7 +272,7 @@ exports[`LoadedRow.tsx renders a row 1`] = `
} }
@media only screen and (max-width:540px) { @media only screen and (max-width:540px) {
.c21 { .c20 {
display: none; display: none;
} }
} }
@ -275,6 +283,7 @@ exports[`LoadedRow.tsx renders a row 1`] = `
display: -webkit-flex; display: -webkit-flex;
display: -ms-flexbox; display: -ms-flexbox;
display: flex; display: flex;
gap: 3px;
-webkit-box-pack: end; -webkit-box-pack: end;
-webkit-justify-content: flex-end; -webkit-justify-content: flex-end;
-ms-flex-pack: end; -ms-flex-pack: end;
@ -407,13 +416,10 @@ exports[`LoadedRow.tsx renders a row 1`] = `
$1.00 $1.00
<div <div
class="c2 c17" class="c2 c17"
>
<div
class="c18"
> >
<svg <svg
aria-label="up" aria-label="up"
class="c19" class="c18"
fill="none" fill="none"
height="14" height="14"
viewBox="0 0 24 24" viewBox="0 0 24 24"
@ -425,9 +431,8 @@ exports[`LoadedRow.tsx renders a row 1`] = `
fill="currentColor" fill="currentColor"
/> />
</svg> </svg>
</div>
<span <span
class="c20" class="c19"
> >
0.00% 0.00%
</span> </span>
@ -436,18 +441,15 @@ exports[`LoadedRow.tsx renders a row 1`] = `
</div> </div>
</div> </div>
<div <div
class="c2 c14 c21" class="c2 c14 c20"
data-testid="percent-change-cell" data-testid="percent-change-cell"
> >
<div <div
class="c5" class="c21"
>
<div
class="c18"
> >
<svg <svg
aria-label="up" aria-label="up"
class="c19" class="c18"
fill="none" fill="none"
height="16" height="16"
viewBox="0 0 24 24" viewBox="0 0 24 24"
@ -459,9 +461,8 @@ exports[`LoadedRow.tsx renders a row 1`] = `
fill="currentColor" fill="currentColor"
/> />
</svg> </svg>
</div>
<span <span
class="c20" class="c19"
> >
0.00% 0.00%
</span> </span>

@ -1,4 +1,4 @@
import { getDeltaArrow } from 'components/Tokens/TokenDetails/PriceChart' import { DeltaArrow } from 'components/Tokens/TokenDetails/Delta'
import { useScreenSize } from 'hooks/useScreenSize' import { useScreenSize } from 'hooks/useScreenSize'
import { Box, BoxProps } from 'nft/components/Box' import { Box, BoxProps } from 'nft/components/Box'
import { Column, Row } from 'nft/components/Flex' import { Column, Row } from 'nft/components/Flex'
@ -352,7 +352,6 @@ const StatsRow = ({ stats, isMobile, ...props }: { stats: GenieCollection; isMob
const floorPriceStr = floorFormatter(stats.stats?.floor_price ?? 0) const floorPriceStr = floorFormatter(stats.stats?.floor_price ?? 0)
// graphQL formatted %age values out of 100, whereas v3 endpoint did a decimal between 0 & 1 // graphQL formatted %age values out of 100, whereas v3 endpoint did a decimal between 0 & 1
const floorChangeStr = Math.round(Math.abs(stats?.stats?.one_day_floor_change ?? 0)) const floorChangeStr = Math.round(Math.abs(stats?.stats?.one_day_floor_change ?? 0))
const arrow = stats?.stats?.one_day_floor_change ? getDeltaArrow(stats.stats.one_day_floor_change) : undefined
const isBagExpanded = useBag((state) => state.bagExpanded) const isBagExpanded = useBag((state) => state.bagExpanded)
const isScreenSize = useScreenSize() const isScreenSize = useScreenSize()
@ -372,7 +371,7 @@ const StatsRow = ({ stats, isMobile, ...props }: { stats: GenieCollection; isMob
{stats.stats?.one_day_floor_change !== undefined ? ( {stats.stats?.one_day_floor_change !== undefined ? (
<StatsItem label="Floor 24H" shouldHide={false}> <StatsItem label="Floor 24H" shouldHide={false}>
<PercentChange isNegative={stats.stats.one_day_floor_change < 0}> <PercentChange isNegative={stats.stats.one_day_floor_change < 0}>
{arrow} <DeltaArrow delta={stats?.stats?.one_day_floor_change} />
{floorChangeStr}% {floorChangeStr}%
</PercentChange> </PercentChange>
</StatsItem> </StatsItem>

@ -1,6 +1,5 @@
import { formatEther } from '@ethersproject/units' import { formatEther } from '@ethersproject/units'
import { ArrowChangeDown } from 'components/Icons/ArrowChangeDown' import { DeltaArrow } from 'components/Tokens/TokenDetails/Delta'
import { ArrowChangeUp } from 'components/Icons/ArrowChangeUp'
import { VerifiedIcon } from 'nft/components/icons' import { VerifiedIcon } from 'nft/components/icons'
import { useIsMobile } from 'nft/hooks' import { useIsMobile } from 'nft/hooks'
import { Denomination } from 'nft/types' import { Denomination } from 'nft/types'
@ -162,11 +161,7 @@ export const ChangeCell = ({ change, children }: { children?: ReactNode; change?
const TextComponent = isMobile ? ThemedText.BodySmall : ThemedText.BodyPrimary const TextComponent = isMobile ? ThemedText.BodySmall : ThemedText.BodyPrimary
return ( return (
<ChangeCellContainer change={change ?? 0}> <ChangeCellContainer change={change ?? 0}>
{!change || change > 0 ? ( <DeltaArrow delta={change} />
<ArrowChangeUp width="16px" height="16px" />
) : (
<ArrowChangeDown width="16px" height="16px" />
)}
<TextComponent color="currentColor">{children || `${change ? Math.abs(Math.round(change)) : 0}%`}</TextComponent> <TextComponent color="currentColor">{children || `${change ? Math.abs(Math.round(change)) : 0}%`}</TextComponent>
</ChangeCellContainer> </ChangeCellContainer>
) )