Commit d6d0a98a authored by cartcrom's avatar cartcrom Committed by GitHub

fix: chart design fixes & style updates (#4341)

* removed ticks outside of hover
* simplifying copyhelper
* finished implementing fred's feedback
* addressed PR comments
* fixed more of fred's feedback
parent 8efc5af2
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="#fff">
<path stroke-linecap="round" stroke-linejoin="round" d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4m14-7-5-5-5 5m5-5v12"/>
</svg>
import { Trans } from '@lingui/macro'
import useCopyClipboard from 'hooks/useCopyClipboard'
import React, { useCallback } from 'react'
import { CheckCircle, Copy } from 'react-feather'
import styled from 'styled-components/macro'
import { LinkStyledButton } from 'theme'
const CopyIcon = styled(LinkStyledButton)`
color: ${({ color, theme }) => color || theme.accentAction};
flex-shrink: 0;
display: flex;
text-decoration: none;
:hover,
:active,
:focus {
text-decoration: none;
color: ${({ color, theme }) => color || theme.accentAction};
}
`
const StyledText = styled.span`
margin-left: 0.25rem;
${({ theme }) => theme.flexRowNoWrap};
align-items: center;
`
const Copied = ({ iconSize }: { iconSize?: number }) => (
<StyledText>
<CheckCircle size={iconSize ?? '16'} />
<StyledText>
<Trans>Copied</Trans>
</StyledText>
</StyledText>
)
const Icon = ({ iconSize }: { iconSize?: number }) => (
<StyledText>
<Copy size={iconSize ?? '16'} />
</StyledText>
)
interface BaseProps {
toCopy: string
color?: string
iconSize?: number
iconPosition?: 'left' | 'right'
}
export type CopyHelperProps = BaseProps & Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, keyof BaseProps>
export default function CopyHelper({ color, toCopy, children, iconSize, iconPosition }: CopyHelperProps) {
const [isCopied, setCopied] = useCopyClipboard()
const copy = useCallback(() => {
setCopied(toCopy)
}, [toCopy, setCopied])
return (
<CopyIcon onClick={copy} color={color}>
{iconPosition === 'left' ? isCopied ? <Copied iconSize={iconSize} /> : <Icon iconSize={iconSize} /> : null}
{iconPosition === 'left' && <>&nbsp;</>}
{isCopied ? '' : children}
{iconPosition === 'right' && <>&nbsp;</>}
{iconPosition === 'right' ? isCopied ? <Copied iconSize={iconSize} /> : <Icon iconSize={iconSize} /> : null}
</CopyIcon>
)
}
import { Trans } from '@lingui/macro' import { Trans } from '@lingui/macro'
import { useWeb3React } from '@web3-react/core' import { useWeb3React } from '@web3-react/core'
import CopyHelper from 'components/AccountDetails/Copy'
import { getConnection, getConnectionName, getIsCoinbaseWallet, getIsMetaMask } from 'connection/utils' import { getConnection, getConnectionName, getIsCoinbaseWallet, getIsMetaMask } from 'connection/utils'
import { Context, useCallback, useContext } from 'react' import { Context, useCallback, useContext } from 'react'
import { ExternalLink as LinkIcon } from 'react-feather' import { ExternalLink as LinkIcon } from 'react-feather'
...@@ -13,7 +12,7 @@ import { isMobile } from 'utils/userAgent' ...@@ -13,7 +12,7 @@ import { isMobile } from 'utils/userAgent'
import { ReactComponent as Close } from '../../assets/images/x.svg' import { ReactComponent as Close } from '../../assets/images/x.svg'
import { clearAllTransactions } from '../../state/transactions/reducer' import { clearAllTransactions } from '../../state/transactions/reducer'
import { ExternalLink, LinkStyledButton, ThemedText } from '../../theme' import { CopyHelper, ExternalLink, LinkStyledButton, ThemedText } from '../../theme'
import { shortenAddress } from '../../utils' import { shortenAddress } from '../../utils'
import { ExplorerDataType, getExplorerLink } from '../../utils/getExplorerLink' import { ExplorerDataType, getExplorerLink } from '../../utils/getExplorerLink'
import { ButtonSecondary } from '../Button' import { ButtonSecondary } from '../Button'
...@@ -130,11 +129,12 @@ const AccountControl = styled.div` ...@@ -130,11 +129,12 @@ const AccountControl = styled.div`
` `
const AddressLink = styled(ExternalLink)` const AddressLink = styled(ExternalLink)`
font-size: 0.825rem;
color: ${({ theme }) => theme.deprecated_text3}; color: ${({ theme }) => theme.deprecated_text3};
margin-left: 1rem; margin-left: 1rem;
font-size: 0.825rem; font-size: 0.825rem;
display: flex; display: flex;
gap: 6px;
text-decoration: none !important;
:hover { :hover {
color: ${({ theme }) => theme.deprecated_text2}; color: ${({ theme }) => theme.deprecated_text2};
} }
...@@ -284,7 +284,7 @@ export default function AccountDetails({ ...@@ -284,7 +284,7 @@ export default function AccountDetails({
<AccountControl> <AccountControl>
<div> <div>
{account && ( {account && (
<CopyHelper toCopy={account} iconPosition="left"> <CopyHelper toCopy={account} gap={6} iconSize={16} fontSize={14}>
<Trans>Copy Address</Trans> <Trans>Copy Address</Trans>
</CopyHelper> </CopyHelper>
)} )}
......
...@@ -11,7 +11,7 @@ interface LineChartProps<T> { ...@@ -11,7 +11,7 @@ interface LineChartProps<T> {
data: T[] data: T[]
getX: (t: T) => number getX: (t: T) => number
getY: (t: T) => number getY: (t: T) => number
marginTop: number marginTop?: number
curve?: CurveFactory curve?: CurveFactory
color?: Color color?: Color
strokeWidth: number strokeWidth: number
......
...@@ -12,6 +12,7 @@ import { useAtom } from 'jotai' ...@@ -12,6 +12,7 @@ import { useAtom } from 'jotai'
import { useCallback, useState } from 'react' import { useCallback, useState } from 'react'
import { ArrowDownRight, ArrowUpRight } from 'react-feather' import { ArrowDownRight, ArrowUpRight } from 'react-feather'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
import { OPACITY_HOVER } from 'theme'
import { import {
dayHourFormatter, dayHourFormatter,
hourFormatter, hourFormatter,
...@@ -32,7 +33,7 @@ const TIME_DISPLAYS: [TimePeriod, string][] = [ ...@@ -32,7 +33,7 @@ const TIME_DISPLAYS: [TimePeriod, string][] = [
[TimePeriod.week, '1W'], [TimePeriod.week, '1W'],
[TimePeriod.month, '1M'], [TimePeriod.month, '1M'],
[TimePeriod.year, '1Y'], [TimePeriod.year, '1Y'],
[TimePeriod.all, 'ALL'], [TimePeriod.all, 'All'],
] ]
type PricePoint = { value: number; timestamp: number } type PricePoint = { value: number; timestamp: number }
...@@ -64,11 +65,6 @@ function getDelta(start: number, current: number) { ...@@ -64,11 +65,6 @@ function getDelta(start: number, current: number) {
return [formattedDelta, <StyledDownArrow size={16} key="arrow-down" />] return [formattedDelta, <StyledDownArrow size={16} key="arrow-down" />]
} }
export const ChartWrapper = styled.div`
position: relative;
overflow: visible;
`
export const ChartHeader = styled.div` export const ChartHeader = styled.div`
position: absolute; position: absolute;
` `
...@@ -111,6 +107,9 @@ const TimeButton = styled.button<{ active: boolean }>` ...@@ -111,6 +107,9 @@ const TimeButton = styled.button<{ active: boolean }>`
border: none; border: none;
cursor: pointer; cursor: pointer;
color: ${({ theme, active }) => (active ? theme.textPrimary : theme.textSecondary)}; color: ${({ theme, active }) => (active ? theme.textPrimary : theme.textSecondary)};
:hover {
${({ active }) => !active && `opacity: ${OPACITY_HOVER};`}
}
` `
function getTicks(startTimestamp: number, endTimestamp: number, numTicks = 5) { function getTicks(startTimestamp: number, endTimestamp: number, numTicks = 5) {
...@@ -142,7 +141,7 @@ function tickFormat( ...@@ -142,7 +141,7 @@ function tickFormat(
} }
} }
const margin = { top: 86, bottom: 32, crosshair: 72 } const margin = { top: 86, bottom: 48, crosshair: 72 }
const timeOptionsHeight = 44 const timeOptionsHeight = 44
const crosshairDateOverhang = 80 const crosshairDateOverhang = 80
...@@ -208,7 +207,7 @@ export function PriceChart({ width, height }: PriceChartProps) { ...@@ -208,7 +207,7 @@ export function PriceChart({ width, height }: PriceChartProps) {
const crosshairAtEdge = !!selected.xCoordinate && selected.xCoordinate > crosshairEdgeMax const crosshairAtEdge = !!selected.xCoordinate && selected.xCoordinate > crosshairEdgeMax
return ( return (
<ChartWrapper> <>
<ChartHeader> <ChartHeader>
<TokenPrice>${selected.pricePoint.value.toFixed(2)}</TokenPrice> <TokenPrice>${selected.pricePoint.value.toFixed(2)}</TokenPrice>
<DeltaContainer> <DeltaContainer>
...@@ -227,6 +226,8 @@ export function PriceChart({ width, height }: PriceChartProps) { ...@@ -227,6 +226,8 @@ export function PriceChart({ width, height }: PriceChartProps) {
width={graphWidth} width={graphWidth}
height={graphHeight} height={graphHeight}
> >
{selected.xCoordinate !== null ? (
<g>
<AxisBottom <AxisBottom
scale={timeScale} scale={timeScale}
stroke={theme.backgroundOutline} stroke={theme.backgroundOutline}
...@@ -243,8 +244,6 @@ export function PriceChart({ width, height }: PriceChartProps) { ...@@ -243,8 +244,6 @@ export function PriceChart({ width, height }: PriceChartProps) {
transform: 'translate(0 -24)', transform: 'translate(0 -24)',
})} })}
/> />
{selected.xCoordinate !== null && (
<g>
<text <text
x={selected.xCoordinate + (crosshairAtEdge ? -4 : 4)} x={selected.xCoordinate + (crosshairAtEdge ? -4 : 4)}
y={margin.crosshair + 10} y={margin.crosshair + 10}
...@@ -271,6 +270,8 @@ export function PriceChart({ width, height }: PriceChartProps) { ...@@ -271,6 +270,8 @@ export function PriceChart({ width, height }: PriceChartProps) {
strokeWidth={2} strokeWidth={2}
/> />
</g> </g>
) : (
<AxisBottom scale={timeScale} stroke={theme.backgroundOutline} top={graphHeight - 1} hideTicks />
)} )}
<rect <rect
x={0} x={0}
...@@ -293,7 +294,7 @@ export function PriceChart({ width, height }: PriceChartProps) { ...@@ -293,7 +294,7 @@ export function PriceChart({ width, height }: PriceChartProps) {
))} ))}
</TimeOptionsContainer> </TimeOptionsContainer>
</TimeOptionsWrapper> </TimeOptionsWrapper>
</ChartWrapper> </>
) )
} }
......
import { Trans } from '@lingui/macro' import { Trans } from '@lingui/macro'
import CopyHelper from 'components/AccountDetails/Copy'
import Column from 'components/Column' import Column from 'components/Column'
import useTheme from 'hooks/useTheme' import useTheme from 'hooks/useTheme'
import { AlertOctagon } from 'react-feather' import { AlertOctagon } from 'react-feather'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
import { ExternalLink, ThemedText } from 'theme' import { ExternalLink, ThemedText } from 'theme'
import { CopyHelper } from '../../theme'
import Modal from '../Modal' import Modal from '../Modal'
const ContentWrapper = styled(Column)` const ContentWrapper = styled(Column)`
align-items: center; align-items: center;
margin: 32px; margin: 32px;
text-align: center; text-align: center;
font-size: 12px;
` `
const WarningIcon = styled(AlertOctagon)` const WarningIcon = styled(AlertOctagon)`
min-height: 22px; min-height: 22px;
...@@ -49,7 +50,14 @@ export default function ConnectedAccountBlocked(props: ConnectedAccountBlockedPr ...@@ -49,7 +50,14 @@ export default function ConnectedAccountBlocked(props: ConnectedAccountBlockedPr
<ThemedText.DeprecatedMain fontSize={12}> <ThemedText.DeprecatedMain fontSize={12}>
<Trans>If you believe this is an error, please send an email including your address to </Trans>{' '} <Trans>If you believe this is an error, please send an email including your address to </Trans>{' '}
</ThemedText.DeprecatedMain> </ThemedText.DeprecatedMain>
<Copy iconSize={12} toCopy="compliance@uniswap.org" color={theme.deprecated_primary1} iconPosition="right"> <Copy
toCopy="compliance@uniswap.org"
fontSize={14}
iconSize={16}
gap={6}
color={theme.deprecated_primary1}
iconPosition="right"
>
compliance@uniswap.org compliance@uniswap.org
</Copy> </Copy>
</ContentWrapper> </ContentWrapper>
......
import useTheme from 'hooks/useTheme'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
import { ChartWrapper, DeltaContainer, TokenPrice } from '../../Charts/PriceChart' import { DeltaContainer, TokenPrice } from '../../Charts/PriceChart'
import { LoadingBubble } from '../loading' import { LoadingBubble } from '../loading'
import { import {
AboutHeader, AboutHeader,
...@@ -18,6 +19,11 @@ import { ...@@ -18,6 +19,11 @@ import {
TopArea, TopArea,
} from './TokenDetail' } from './TokenDetail'
const LoadingChartContainer = styled(ChartContainer)`
height: 336px;
overflow: hidden;
`
/* Loading state bubbles */ /* Loading state bubbles */
const LoadingDetailBubble = styled(LoadingBubble)` const LoadingDetailBubble = styled(LoadingBubble)`
height: 16px; height: 16px;
...@@ -71,6 +77,15 @@ const Space = styled.div<{ heightSize: number }>` ...@@ -71,6 +77,15 @@ const Space = styled.div<{ heightSize: number }>`
height: ${({ heightSize }) => `${heightSize}px`}; height: ${({ heightSize }) => `${heightSize}px`};
` `
function Wave() {
const theme = useTheme()
return (
<svg width="416" height="160" xmlns="http://www.w3.org/2000/svg">
<path d="M 0 80 Q 104 10, 208 80 T 416 80" stroke={theme.backgroundOutline} fill="transparent" strokeWidth="2" />
</svg>
)
}
/* Loading State: row component with loading bubbles */ /* Loading State: row component with loading bubbles */
export default function LoadingTokenDetail() { export default function LoadingTokenDetail() {
return ( return (
...@@ -85,35 +100,23 @@ export default function LoadingTokenDetail() { ...@@ -85,35 +100,23 @@ export default function LoadingTokenDetail() {
<TitleLoadingBubble /> <TitleLoadingBubble />
</TokenNameCell> </TokenNameCell>
</TokenInfoContainer> </TokenInfoContainer>
<ChartContainer>
<ChartWrapper>
<ChartHeader>
<TokenPrice> <TokenPrice>
<PriceLoadingBubble /> <PriceLoadingBubble />
</TokenPrice> </TokenPrice>
<DeltaContainer> <DeltaContainer>
<Space heightSize={20} /> <Space heightSize={20} />
</DeltaContainer> </DeltaContainer>
</ChartHeader> <LoadingChartContainer>
<div>
<ChartAnimation> <ChartAnimation>
<svg width="416" height="160" xmlns="http://www.w3.org/2000/svg"> <Wave />
<path d="M 0 80 Q 104 10, 208 80 T 416 80" stroke="#2e3138" fill="transparent" strokeWidth="2" /> <Wave />
</svg> <Wave />
<svg width="416" height="160" xmlns="http://www.w3.org/2000/svg"> <Wave />
<path d="M 0 80 Q 104 10, 208 80 T 416 80" stroke="#2e3138" fill="transparent" strokeWidth="2" /> <Wave />
</svg>
<svg width="416" height="160" xmlns="http://www.w3.org/2000/svg">
<path d="M 0 80 Q 104 10, 208 80 T 416 80" stroke="#2e3138" fill="transparent" strokeWidth="2" />
</svg>
<svg width="416" height="160" xmlns="http://www.w3.org/2000/svg">
<path d="M 0 80 Q 104 10, 208 80 T 416 80" stroke="#2e3138" fill="transparent" strokeWidth="2" />
</svg>
<svg width="416" height="160" xmlns="http://www.w3.org/2000/svg">
<path d="M 0 80 Q 104 10, 208 80 T 416 80" stroke="#2e3138" fill="transparent" strokeWidth="2" />
</svg>
</ChartAnimation> </ChartAnimation>
</ChartWrapper> </div>
</ChartContainer> </LoadingChartContainer>
<Space heightSize={32} /> <Space heightSize={32} />
</ChartHeader> </ChartHeader>
<AboutSection> <AboutSection>
......
import { useOnClickOutside } from 'hooks/useOnClickOutside' import { useOnClickOutside } from 'hooks/useOnClickOutside'
import { darken } from 'polished' import { useRef } from 'react'
import { useRef, useState } from 'react' import { Twitter } from 'react-feather'
import { Check, Link, Share, Twitter } from 'react-feather'
import { useModalIsOpen, useToggleModal } from 'state/application/hooks' import { useModalIsOpen, useToggleModal } from 'state/application/hooks'
import { ApplicationModal } from 'state/application/reducer' import { ApplicationModal } from 'state/application/reducer'
import styled, { useTheme } from 'styled-components/macro' import styled, { useTheme } from 'styled-components/macro'
import { Z_INDEX } from 'theme' import { ClickableStyle, CopyHelperRefType, OPACITY_CLICK, Z_INDEX } from 'theme'
import { colors } from 'theme/colors'
import { opacify } from 'theme/utils'
import { ReactComponent as ShareIcon } from '../../../assets/svg/share.svg'
import { CopyHelper } from '../../../theme'
const TWITTER_WIDTH = 560 const TWITTER_WIDTH = 560
const TWITTER_HEIGHT = 480 const TWITTER_HEIGHT = 480
const ShareButtonDisplay = styled.div` const ShareButtonDisplay = styled.div`
display: flex; display: flex;
cursor: pointer;
position: relative; position: relative;
z-index: ${Z_INDEX.dropdown}; `
&:hover { const Share = styled(ShareIcon)<{ open: boolean }>`
color: ${({ theme }) => darken(0.1, theme.textSecondary)}; stroke: ${({ theme }) => theme.textSecondary};
} height: 24px;
width: 24px;
${ClickableStyle}
${({ open }) => open && `opacity: ${OPACITY_CLICK} !important`};
` `
const ShareActions = styled.div` const ShareActions = styled.div`
position: absolute; position: absolute;
top: 28px; z-index: ${Z_INDEX.dropdown};
width: 240px;
top: 36px;
right: 0px; right: 0px;
padding: 8px 0px; justify-content: center;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: fit-content;
overflow: auto; overflow: auto;
padding: 8px;
background-color: ${({ theme }) => theme.backgroundSurface}; background-color: ${({ theme }) => theme.backgroundSurface};
border: 1px solid ${({ theme }) => theme.backgroundOutline}; border: 0.5px solid ${({ theme }) => theme.backgroundOutline};
box-shadow: ${({ theme }) => theme.flyoutDropShadow}; box-shadow: ${({ theme }) => theme.flyoutDropShadow};
border-radius: 12px; border-radius: 12px;
` `
const ShareAction = styled.div` const ShareAction = styled.div`
display: flex; display: flex;
align-items: center; align-items: center;
padding: 12px 16px; padding: 8px;
border-radius: 8px;
font-size: 16px; font-size: 16px;
gap: 8px; font-weight: 400;
width: 200px; gap: 12px;
height: 48px; height: 40px;
color: ${({ theme }) => theme.textPrimary}; color: ${({ theme }) => theme.textPrimary};
cursor: pointer; cursor: pointer;
:hover {
&:hover { background-color: ${({ theme }) => opacify(10, theme.darkMode ? colors.gray200 : colors.gray300)};
background-color: ${({ theme }) => theme.backgroundModule};
} }
` `
const LinkCopied = styled.div<{ show: boolean }>`
display: ${({ show }) => (show ? 'flex' : 'none')};
width: 328px;
height: 72px;
color: ${({ theme }) => theme.textPrimary};
background-color: ${({ theme }) => theme.backgroundBackdrop};
justify-content: flex-start;
align-items: center;
padding: 24px 16px;
position: absolute;
right: 32px;
bottom: 32px;
font-size: 14px;
gap: 8px;
border: 1px solid rgba(153, 161, 189, 0.08);
box-shadow: ${({ theme }) => theme.flyoutDropShadow};
border-radius: 20px;
animation: floatIn 0s ease-in 3s forwards;
@keyframes floatIn {
to {
width: 0;
height: 0;
overflow: hidden;
display: none;
}
}
`
interface TokenInfo { interface TokenInfo {
tokenName: string tokenName: string
tokenSymbol: string tokenSymbol: string
...@@ -89,7 +70,6 @@ export default function ShareButton(tokenInfo: TokenInfo) { ...@@ -89,7 +70,6 @@ export default function ShareButton(tokenInfo: TokenInfo) {
const open = useModalIsOpen(ApplicationModal.SHARE) const open = useModalIsOpen(ApplicationModal.SHARE)
const toggleShare = useToggleModal(ApplicationModal.SHARE) const toggleShare = useToggleModal(ApplicationModal.SHARE)
useOnClickOutside(node, open ? toggleShare : undefined) useOnClickOutside(node, open ? toggleShare : undefined)
const [showCopied, setShowCopied] = useState(false)
const positionX = (window.screen.width - TWITTER_WIDTH) / 2 const positionX = (window.screen.width - TWITTER_WIDTH) / 2
const positionY = (window.screen.height - TWITTER_HEIGHT) / 2 const positionY = (window.screen.height - TWITTER_HEIGHT) / 2
...@@ -101,41 +81,32 @@ export default function ShareButton(tokenInfo: TokenInfo) { ...@@ -101,41 +81,32 @@ export default function ShareButton(tokenInfo: TokenInfo) {
`left=${positionX}, top=${positionY}, width=${TWITTER_WIDTH}, height=${TWITTER_HEIGHT}` `left=${positionX}, top=${positionY}, width=${TWITTER_WIDTH}, height=${TWITTER_HEIGHT}`
) )
} }
const copyLink = () => {
navigator.clipboard.writeText(window.location.href).then( const copyHelperRef = useRef<CopyHelperRefType>(null)
function handleClipboardWriteSuccess() {
setShowCopied(true)
toggleShare()
setTimeout(() => setShowCopied(false), 3000)
},
function error() {
console.error('Clipboard copy failed.')
}
)
}
return ( return (
<>
<ShareButtonDisplay ref={node}> <ShareButtonDisplay ref={node}>
<Share size={18} onClick={toggleShare} aria-label={`ShareOptions`} /> <Share onClick={toggleShare} aria-label={`ShareOptions`} open={open} />
{open && ( {open && (
<ShareActions> <ShareActions>
<ShareAction onClick={copyLink}> <ShareAction onClick={() => copyHelperRef.current?.forceCopy()}>
<Link color={theme.textSecondary} size={18} /> <CopyHelper
Copy link link
color={theme.textPrimary}
iconPosition="left"
toCopy={window.location.href}
ref={copyHelperRef}
>
Copy Link
</CopyHelper>
</ShareAction> </ShareAction>
<ShareAction onClick={shareTweet}> <ShareAction onClick={shareTweet}>
<Twitter color={theme.textSecondary} size={18} /> <Twitter color={theme.textPrimary} size={20} strokeWidth={1.5} />
Share to Twitter Share to Twitter
</ShareAction> </ShareAction>
</ShareActions> </ShareActions>
)} )}
</ShareButtonDisplay> </ShareButtonDisplay>
<LinkCopied show={showCopied}>
<Check color={theme.accentSuccess} />
Link Copied
</LinkCopied>
</>
) )
} }
...@@ -8,14 +8,13 @@ import { getChainInfo } from 'constants/chainInfo' ...@@ -8,14 +8,13 @@ import { getChainInfo } from 'constants/chainInfo'
import { checkWarning } from 'constants/tokenSafety' import { checkWarning } from 'constants/tokenSafety'
import { useCurrency, useIsUserAddedToken, useToken } from 'hooks/Tokens' import { useCurrency, useIsUserAddedToken, useToken } from 'hooks/Tokens'
import { useAtomValue } from 'jotai/utils' import { useAtomValue } from 'jotai/utils'
import { darken } from 'polished'
import { useCallback } from 'react' import { useCallback } from 'react'
import { useState } from 'react' import { useState } from 'react'
import { ArrowLeft, Copy, Heart } from 'react-feather' import { ArrowLeft, Heart } from 'react-feather'
import { Link, useNavigate } from 'react-router-dom' import { Link, useNavigate } from 'react-router-dom'
import styled, { useTheme } from 'styled-components/macro' import styled from 'styled-components/macro'
import { ClickableStyle, CopyContractAddress } from 'theme'
import { MOBILE_MEDIA_BREAKPOINT } from '../constants'
import { favoritesAtom, useToggleFavorite } from '../state' import { favoritesAtom, useToggleFavorite } from '../state'
import { ClickFavorited } from '../TokenTable/TokenRow' import { ClickFavorited } from '../TokenTable/TokenRow'
import Resource from './Resource' import Resource from './Resource'
...@@ -62,10 +61,6 @@ const ContractAddress = styled.button` ...@@ -62,10 +61,6 @@ const ContractAddress = styled.button`
border: none; border: none;
padding: 0px; padding: 0px;
cursor: pointer; cursor: pointer;
&:hover {
color: ${({ theme }) => darken(0.1, theme.textPrimary)};
}
` `
export const ContractAddressSection = styled.div` export const ContractAddressSection = styled.div`
padding: 24px 0px; padding: 24px 0px;
...@@ -114,7 +109,7 @@ export const TokenNameCell = styled.div` ...@@ -114,7 +109,7 @@ export const TokenNameCell = styled.div`
` `
const TokenActions = styled.div` const TokenActions = styled.div`
display: flex; display: flex;
gap: 24px; gap: 16px;
color: ${({ theme }) => theme.textSecondary}; color: ${({ theme }) => theme.textSecondary};
` `
export const TokenInfoContainer = styled.div` export const TokenInfoContainer = styled.div`
...@@ -132,17 +127,6 @@ export const ResourcesContainer = styled.div` ...@@ -132,17 +127,6 @@ export const ResourcesContainer = styled.div`
display: flex; display: flex;
gap: 14px; gap: 14px;
` `
const FullAddress = styled.span`
@media only screen and (max-width: ${MOBILE_MEDIA_BREAKPOINT}) {
display: none;
}
`
const TruncatedAddress = styled.span`
display: none;
@media only screen and (max-width: ${MOBILE_MEDIA_BREAKPOINT}) {
display: flex;
}
`
const NetworkBadge = styled.div<{ networkColor?: string; backgroundColor?: string }>` const NetworkBadge = styled.div<{ networkColor?: string; backgroundColor?: string }>`
border-radius: 5px; border-radius: 5px;
padding: 4px 8px; padding: 4px 8px;
...@@ -152,9 +136,15 @@ const NetworkBadge = styled.div<{ networkColor?: string; backgroundColor?: strin ...@@ -152,9 +136,15 @@ const NetworkBadge = styled.div<{ networkColor?: string; backgroundColor?: strin
color: ${({ theme, networkColor }) => networkColor ?? theme.textPrimary}; color: ${({ theme, networkColor }) => networkColor ?? theme.textPrimary};
background-color: ${({ theme, backgroundColor }) => backgroundColor ?? theme.backgroundSurface}; background-color: ${({ theme, backgroundColor }) => backgroundColor ?? theme.backgroundSurface};
` `
const FavoriteIcon = styled(Heart)<{ isFavorited: boolean }>`
${ClickableStyle}
height: 22px;
width: 24px;
color: ${({ isFavorited, theme }) => (isFavorited ? theme.accentAction : theme.textSecondary)};
fill: ${({ isFavorited, theme }) => (isFavorited ? theme.accentAction : 'transparent')};
`
export default function LoadedTokenDetail({ address }: { address: string }) { export default function LoadedTokenDetail({ address }: { address: string }) {
const theme = useTheme()
const token = useToken(address) const token = useToken(address)
const currency = useCurrency(address) const currency = useCurrency(address)
const favoriteTokens = useAtomValue<string[]>(favoritesAtom) const favoriteTokens = useAtomValue<string[]>(favoritesAtom)
...@@ -184,7 +174,6 @@ export default function LoadedTokenDetail({ address }: { address: string }) { ...@@ -184,7 +174,6 @@ export default function LoadedTokenDetail({ address }: { address: string }) {
'Ethereum is a decentralized computing platform that uses ETH (Ether) to pay transaction fees (gas). Developers can use Ethereum to run decentralized applications (dApps) and issue new crypto assets, known as Ethereum tokens.' 'Ethereum is a decentralized computing platform that uses ETH (Ether) to pay transaction fees (gas). Developers can use Ethereum to run decentralized applications (dApps) and issue new crypto assets, known as Ethereum tokens.'
const tokenMarketCap = '23.02B' const tokenMarketCap = '23.02B'
const tokenVolume = '1.6B' const tokenVolume = '1.6B'
const truncatedTokenAddress = `${address.slice(0, 4)}...${address.slice(-3)}`
return ( return (
<TopArea> <TopArea>
...@@ -196,7 +185,7 @@ export default function LoadedTokenDetail({ address }: { address: string }) { ...@@ -196,7 +185,7 @@ export default function LoadedTokenDetail({ address }: { address: string }) {
<TokenNameCell> <TokenNameCell>
<CurrencyLogo currency={currency} size={'32px'} /> <CurrencyLogo currency={currency} size={'32px'} />
{tokenName} <TokenSymbol>{tokenSymbol}</TokenSymbol> {tokenName} <TokenSymbol>{tokenSymbol}</TokenSymbol>
{!warning && <VerifiedIcon size="24px" />} {!warning && <VerifiedIcon size="20px" />}
{networkBadgebackgroundColor && ( {networkBadgebackgroundColor && (
<NetworkBadge networkColor={chainInfo?.color} backgroundColor={networkBadgebackgroundColor}> <NetworkBadge networkColor={chainInfo?.color} backgroundColor={networkBadgebackgroundColor}>
{networkLabel} {networkLabel}
...@@ -206,11 +195,7 @@ export default function LoadedTokenDetail({ address }: { address: string }) { ...@@ -206,11 +195,7 @@ export default function LoadedTokenDetail({ address }: { address: string }) {
<TokenActions> <TokenActions>
<ShareButton tokenName={tokenName} tokenSymbol={tokenSymbol} /> <ShareButton tokenName={tokenName} tokenSymbol={tokenSymbol} />
<ClickFavorited onClick={toggleFavorite}> <ClickFavorited onClick={toggleFavorite}>
<Heart <FavoriteIcon isFavorited={isFavorited} />
size={15}
color={isFavorited ? theme.accentAction : theme.textSecondary}
fill={isFavorited ? theme.accentAction : 'transparent'}
/>
</ClickFavorited> </ClickFavorited>
</TokenActions> </TokenActions>
</TokenInfoContainer> </TokenInfoContainer>
...@@ -235,7 +220,7 @@ export default function LoadedTokenDetail({ address }: { address: string }) { ...@@ -235,7 +220,7 @@ export default function LoadedTokenDetail({ address }: { address: string }) {
</Stat> </Stat>
<Stat> <Stat>
{/* TODO: connect to chart's selected time */} {/* TODO: connect to chart's selected time */}
1h volume 24H volume
<StatPrice>${tokenVolume}</StatPrice> <StatPrice>${tokenVolume}</StatPrice>
</Stat> </Stat>
</StatPair> </StatPair>
...@@ -253,10 +238,8 @@ export default function LoadedTokenDetail({ address }: { address: string }) { ...@@ -253,10 +238,8 @@ export default function LoadedTokenDetail({ address }: { address: string }) {
<ContractAddressSection> <ContractAddressSection>
<Contract> <Contract>
Contract Address Contract Address
<ContractAddress onClick={() => navigator.clipboard.writeText(address)}> <ContractAddress>
<FullAddress>{address}</FullAddress> <CopyContractAddress address={address} />
<TruncatedAddress>{truncatedTokenAddress}</TruncatedAddress>
<Copy size={13} color={theme.textSecondary} />
</ContractAddress> </ContractAddress>
</Contract> </Contract>
</ContractAddressSection> </ContractAddressSection>
......
/**
* @generated SignedSource<<0becdf63598262462f6fa0cabb891ad0>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest, Query } from 'relay-runtime';
export type AllV3TicksQuery$variables = {
poolAddress: string;
skip: number;
};
export type AllV3TicksQuery$data = {
readonly ticks: ReadonlyArray<{
readonly liquidityNet: any;
readonly price0: any;
readonly price1: any;
readonly tick: any;
}>;
};
export type AllV3TicksQuery = {
response: AllV3TicksQuery$data;
variables: AllV3TicksQuery$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "poolAddress"
},
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "skip"
}
],
v1 = [
{
"kind": "Literal",
"name": "first",
"value": 1000
},
{
"kind": "Literal",
"name": "orderBy",
"value": "tickIdx"
},
{
"kind": "Variable",
"name": "skip",
"variableName": "skip"
},
{
"fields": [
{
"kind": "Variable",
"name": "poolAddress",
"variableName": "poolAddress"
}
],
"kind": "ObjectValue",
"name": "where"
}
],
v2 = {
"alias": "tick",
"args": null,
"kind": "ScalarField",
"name": "tickIdx",
"storageKey": null
},
v3 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "liquidityNet",
"storageKey": null
},
v4 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "price0",
"storageKey": null
},
v5 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "price1",
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "AllV3TicksQuery",
"selections": [
{
"alias": null,
"args": (v1/*: any*/),
"concreteType": "Tick",
"kind": "LinkedField",
"name": "ticks",
"plural": true,
"selections": [
(v2/*: any*/),
(v3/*: any*/),
(v4/*: any*/),
(v5/*: any*/)
],
"storageKey": null
}
],
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "AllV3TicksQuery",
"selections": [
{
"alias": null,
"args": (v1/*: any*/),
"concreteType": "Tick",
"kind": "LinkedField",
"name": "ticks",
"plural": true,
"selections": [
(v2/*: any*/),
(v3/*: any*/),
(v4/*: any*/),
(v5/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "9f2d65b1e565e3d0ecbe7b1f908ebc83",
"id": null,
"metadata": {},
"name": "AllV3TicksQuery",
"operationKind": "query",
"text": "query AllV3TicksQuery(\n $poolAddress: String!\n $skip: Int!\n) {\n ticks(first: 1000, skip: $skip, where: {poolAddress: $poolAddress}, orderBy: tickIdx) {\n tick: tickIdx\n liquidityNet\n price0\n price1\n id\n }\n}\n"
}
};
})();
(node as any).hash = "82709c11c929a8eb6caf2ab1df2b99cc";
export default node;
/**
* @generated SignedSource<<5761481cf3bba524864626a7f965c0d7>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest, Query } from 'relay-runtime';
export type FeeTierDistributionQuery$variables = {
token0: string;
token1: string;
};
export type FeeTierDistributionQuery$data = {
readonly _meta: {
readonly block: {
readonly number: number;
};
} | null;
readonly asToken0: ReadonlyArray<{
readonly feeTier: any;
readonly totalValueLockedToken0: any;
readonly totalValueLockedToken1: any;
}>;
readonly asToken1: ReadonlyArray<{
readonly feeTier: any;
readonly totalValueLockedToken0: any;
readonly totalValueLockedToken1: any;
}>;
};
export type FeeTierDistributionQuery = {
response: FeeTierDistributionQuery$data;
variables: FeeTierDistributionQuery$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "token0"
},
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "token1"
}
],
v1 = {
"alias": null,
"args": null,
"concreteType": "_Meta_",
"kind": "LinkedField",
"name": "_meta",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "_Block_",
"kind": "LinkedField",
"name": "block",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "number",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
},
v2 = {
"kind": "Literal",
"name": "orderBy",
"value": "totalValueLockedToken0"
},
v3 = {
"kind": "Literal",
"name": "orderDirection",
"value": "desc"
},
v4 = [
(v2/*: any*/),
(v3/*: any*/),
{
"fields": [
{
"kind": "Variable",
"name": "token0",
"variableName": "token0"
},
{
"kind": "Variable",
"name": "token1",
"variableName": "token1"
}
],
"kind": "ObjectValue",
"name": "where"
}
],
v5 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "feeTier",
"storageKey": null
},
v6 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "totalValueLockedToken0",
"storageKey": null
},
v7 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "totalValueLockedToken1",
"storageKey": null
},
v8 = [
(v5/*: any*/),
(v6/*: any*/),
(v7/*: any*/)
],
v9 = [
(v2/*: any*/),
(v3/*: any*/),
{
"fields": [
{
"kind": "Variable",
"name": "token0",
"variableName": "token1"
},
{
"kind": "Variable",
"name": "token1",
"variableName": "token0"
}
],
"kind": "ObjectValue",
"name": "where"
}
],
v10 = [
(v5/*: any*/),
(v6/*: any*/),
(v7/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "FeeTierDistributionQuery",
"selections": [
(v1/*: any*/),
{
"alias": "asToken0",
"args": (v4/*: any*/),
"concreteType": "Pool",
"kind": "LinkedField",
"name": "pools",
"plural": true,
"selections": (v8/*: any*/),
"storageKey": null
},
{
"alias": "asToken1",
"args": (v9/*: any*/),
"concreteType": "Pool",
"kind": "LinkedField",
"name": "pools",
"plural": true,
"selections": (v8/*: any*/),
"storageKey": null
}
],
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "FeeTierDistributionQuery",
"selections": [
(v1/*: any*/),
{
"alias": "asToken0",
"args": (v4/*: any*/),
"concreteType": "Pool",
"kind": "LinkedField",
"name": "pools",
"plural": true,
"selections": (v10/*: any*/),
"storageKey": null
},
{
"alias": "asToken1",
"args": (v9/*: any*/),
"concreteType": "Pool",
"kind": "LinkedField",
"name": "pools",
"plural": true,
"selections": (v10/*: any*/),
"storageKey": null
}
]
},
"params": {
"cacheID": "d989fcfb8fc9ef13bdc6de811e574284",
"id": null,
"metadata": {},
"name": "FeeTierDistributionQuery",
"operationKind": "query",
"text": "query FeeTierDistributionQuery(\n $token0: String!\n $token1: String!\n) {\n _meta {\n block {\n number\n }\n }\n asToken0: pools(orderBy: totalValueLockedToken0, orderDirection: desc, where: {token0: $token0, token1: $token1}) {\n feeTier\n totalValueLockedToken0\n totalValueLockedToken1\n id\n }\n asToken1: pools(orderBy: totalValueLockedToken0, orderDirection: desc, where: {token0: $token1, token1: $token0}) {\n feeTier\n totalValueLockedToken0\n totalValueLockedToken1\n id\n }\n}\n"
}
};
})();
(node as any).hash = "ac9cd4bfdc24db90dbb2bf8a7508009f";
export default node;
This diff is collapsed.
/**
* @generated SignedSource<<0becdf63598262462f6fa0cabb891ad0>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest, Query } from 'relay-runtime';
export type AllV3TicksQuery$variables = {
poolAddress: string;
skip: number;
};
export type AllV3TicksQuery$data = {
readonly ticks: ReadonlyArray<{
readonly liquidityNet: any;
readonly price0: any;
readonly price1: any;
readonly tick: any;
}>;
};
export type AllV3TicksQuery = {
response: AllV3TicksQuery$data;
variables: AllV3TicksQuery$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "poolAddress"
},
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "skip"
}
],
v1 = [
{
"kind": "Literal",
"name": "first",
"value": 1000
},
{
"kind": "Literal",
"name": "orderBy",
"value": "tickIdx"
},
{
"kind": "Variable",
"name": "skip",
"variableName": "skip"
},
{
"fields": [
{
"kind": "Variable",
"name": "poolAddress",
"variableName": "poolAddress"
}
],
"kind": "ObjectValue",
"name": "where"
}
],
v2 = {
"alias": "tick",
"args": null,
"kind": "ScalarField",
"name": "tickIdx",
"storageKey": null
},
v3 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "liquidityNet",
"storageKey": null
},
v4 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "price0",
"storageKey": null
},
v5 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "price1",
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "AllV3TicksQuery",
"selections": [
{
"alias": null,
"args": (v1/*: any*/),
"concreteType": "Tick",
"kind": "LinkedField",
"name": "ticks",
"plural": true,
"selections": [
(v2/*: any*/),
(v3/*: any*/),
(v4/*: any*/),
(v5/*: any*/)
],
"storageKey": null
}
],
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "AllV3TicksQuery",
"selections": [
{
"alias": null,
"args": (v1/*: any*/),
"concreteType": "Tick",
"kind": "LinkedField",
"name": "ticks",
"plural": true,
"selections": [
(v2/*: any*/),
(v3/*: any*/),
(v4/*: any*/),
(v5/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "9f2d65b1e565e3d0ecbe7b1f908ebc83",
"id": null,
"metadata": {},
"name": "AllV3TicksQuery",
"operationKind": "query",
"text": "query AllV3TicksQuery(\n $poolAddress: String!\n $skip: Int!\n) {\n ticks(first: 1000, skip: $skip, where: {poolAddress: $poolAddress}, orderBy: tickIdx) {\n tick: tickIdx\n liquidityNet\n price0\n price1\n id\n }\n}\n"
}
};
})();
(node as any).hash = "82709c11c929a8eb6caf2ab1df2b99cc";
export default node;
/**
* @generated SignedSource<<5761481cf3bba524864626a7f965c0d7>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest, Query } from 'relay-runtime';
export type FeeTierDistributionQuery$variables = {
token0: string;
token1: string;
};
export type FeeTierDistributionQuery$data = {
readonly _meta: {
readonly block: {
readonly number: number;
};
} | null;
readonly asToken0: ReadonlyArray<{
readonly feeTier: any;
readonly totalValueLockedToken0: any;
readonly totalValueLockedToken1: any;
}>;
readonly asToken1: ReadonlyArray<{
readonly feeTier: any;
readonly totalValueLockedToken0: any;
readonly totalValueLockedToken1: any;
}>;
};
export type FeeTierDistributionQuery = {
response: FeeTierDistributionQuery$data;
variables: FeeTierDistributionQuery$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "token0"
},
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "token1"
}
],
v1 = {
"alias": null,
"args": null,
"concreteType": "_Meta_",
"kind": "LinkedField",
"name": "_meta",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "_Block_",
"kind": "LinkedField",
"name": "block",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "number",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
},
v2 = {
"kind": "Literal",
"name": "orderBy",
"value": "totalValueLockedToken0"
},
v3 = {
"kind": "Literal",
"name": "orderDirection",
"value": "desc"
},
v4 = [
(v2/*: any*/),
(v3/*: any*/),
{
"fields": [
{
"kind": "Variable",
"name": "token0",
"variableName": "token0"
},
{
"kind": "Variable",
"name": "token1",
"variableName": "token1"
}
],
"kind": "ObjectValue",
"name": "where"
}
],
v5 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "feeTier",
"storageKey": null
},
v6 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "totalValueLockedToken0",
"storageKey": null
},
v7 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "totalValueLockedToken1",
"storageKey": null
},
v8 = [
(v5/*: any*/),
(v6/*: any*/),
(v7/*: any*/)
],
v9 = [
(v2/*: any*/),
(v3/*: any*/),
{
"fields": [
{
"kind": "Variable",
"name": "token0",
"variableName": "token1"
},
{
"kind": "Variable",
"name": "token1",
"variableName": "token0"
}
],
"kind": "ObjectValue",
"name": "where"
}
],
v10 = [
(v5/*: any*/),
(v6/*: any*/),
(v7/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "FeeTierDistributionQuery",
"selections": [
(v1/*: any*/),
{
"alias": "asToken0",
"args": (v4/*: any*/),
"concreteType": "Pool",
"kind": "LinkedField",
"name": "pools",
"plural": true,
"selections": (v8/*: any*/),
"storageKey": null
},
{
"alias": "asToken1",
"args": (v9/*: any*/),
"concreteType": "Pool",
"kind": "LinkedField",
"name": "pools",
"plural": true,
"selections": (v8/*: any*/),
"storageKey": null
}
],
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "FeeTierDistributionQuery",
"selections": [
(v1/*: any*/),
{
"alias": "asToken0",
"args": (v4/*: any*/),
"concreteType": "Pool",
"kind": "LinkedField",
"name": "pools",
"plural": true,
"selections": (v10/*: any*/),
"storageKey": null
},
{
"alias": "asToken1",
"args": (v9/*: any*/),
"concreteType": "Pool",
"kind": "LinkedField",
"name": "pools",
"plural": true,
"selections": (v10/*: any*/),
"storageKey": null
}
]
},
"params": {
"cacheID": "d989fcfb8fc9ef13bdc6de811e574284",
"id": null,
"metadata": {},
"name": "FeeTierDistributionQuery",
"operationKind": "query",
"text": "query FeeTierDistributionQuery(\n $token0: String!\n $token1: String!\n) {\n _meta {\n block {\n number\n }\n }\n asToken0: pools(orderBy: totalValueLockedToken0, orderDirection: desc, where: {token0: $token0, token1: $token1}) {\n feeTier\n totalValueLockedToken0\n totalValueLockedToken1\n id\n }\n asToken1: pools(orderBy: totalValueLockedToken0, orderDirection: desc, where: {token0: $token1, token1: $token0}) {\n feeTier\n totalValueLockedToken0\n totalValueLockedToken1\n id\n }\n}\n"
}
};
})();
(node as any).hash = "ac9cd4bfdc24db90dbb2bf8a7508009f";
export default node;
...@@ -156,14 +156,7 @@ export default function App() { ...@@ -156,14 +156,7 @@ export default function App() {
{exploreFlag === ExploreVariant.Enabled && ( {exploreFlag === ExploreVariant.Enabled && (
<> <>
<Route path="/explore" element={<Explore />} /> <Route path="/explore" element={<Explore />} />
<Route <Route path="/tokens/:tokenAddress" element={<TokenDetails />} />
path="/tokens/:tokenAddress"
element={
<Suspense fallback={<LazyLoadSpinner />}>
<TokenDetails />
</Suspense>
}
/>
</> </>
)} )}
<Route <Route
......
This diff is collapsed.
import { Trans } from '@lingui/macro'
import { outboundLink } from 'components/analytics' import { outboundLink } from 'components/analytics'
import { MOBILE_MEDIA_BREAKPOINT } from 'components/Explore/constants'
import useCopyClipboard from 'hooks/useCopyClipboard' import useCopyClipboard from 'hooks/useCopyClipboard'
import React, { HTMLProps, useCallback } from 'react' import React, { forwardRef, HTMLProps, ReactNode, useCallback, useImperativeHandle } from 'react'
import { ArrowLeft, Copy, ExternalLink as LinkIconFeather, Trash, X } from 'react-feather' import {
ArrowLeft,
CheckCircle,
Copy,
ExternalLink as ExternalLinkIconFeather,
Link as LinkIconFeather,
Trash,
X,
} from 'react-feather'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import styled, { css, keyframes } from 'styled-components/macro' import styled, { css, keyframes } from 'styled-components/macro'
import { ReactComponent as TooltipTriangle } from '../assets/svg/tooltip_triangle.svg' import { ReactComponent as TooltipTriangle } from '../assets/svg/tooltip_triangle.svg'
import { anonymizeLink } from '../utils/anonymizeLink' import { anonymizeLink } from '../utils/anonymizeLink'
import { Color } from './styled'
export const ButtonText = styled.button` // TODO: Break this file into a components folder
outline: none;
border: none;
font-size: inherit;
padding: 0;
margin: 0;
background: none;
cursor: pointer;
:hover {
opacity: 0.7;
}
:focus {
text-decoration: underline;
}
`
export const CloseIcon = styled(X)<{ onClick: () => void }>` export const CloseIcon = styled(X)<{ onClick: () => void }>`
cursor: pointer; cursor: pointer;
...@@ -68,23 +63,48 @@ export const LinkStyledButton = styled.button<{ disabled?: boolean }>` ...@@ -68,23 +63,48 @@ export const LinkStyledButton = styled.button<{ disabled?: boolean }>`
} }
` `
export const LinkStyle = css` export const OPACITY_HOVER = 0.6
export const OPACITY_CLICK = 0.4
export const ButtonText = styled.button`
outline: none;
border: none;
font-size: inherit;
padding: 0;
margin: 0;
background: none;
cursor: pointer;
:hover {
opacity: ${OPACITY_HOVER};
}
:focus {
text-decoration: underline;
}
`
export const ClickableStyle = css`
text-decoration: none; text-decoration: none;
color: ${({ theme }) => theme.accentAction};
stroke: ${({ theme }) => theme.accentAction};
cursor: pointer; cursor: pointer;
font-weight: 500;
:hover { :hover {
opacity: 0.6; opacity: ${OPACITY_HOVER};
} }
:active { :active {
opacity: 0.4; opacity: ${OPACITY_CLICK};
} }
` `
export const LinkStyle = css`
color: ${({ theme }) => theme.accentAction};
stroke: ${({ theme }) => theme.accentAction};
font-weight: 500;
`
// An internal link from the react-router-dom library that is correctly styled // An internal link from the react-router-dom library that is correctly styled
export const StyledInternalLink = styled(Link)` export const StyledInternalLink = styled(Link)`
${ClickableStyle}
${LinkStyle} ${LinkStyle}
` `
...@@ -94,27 +114,21 @@ const LinkIconWrapper = styled.a` ...@@ -94,27 +114,21 @@ const LinkIconWrapper = styled.a`
display: flex; display: flex;
` `
const CopyIconWrapper = styled.div`
text-decoration: none;
cursor: pointer;
align-items: center;
justify-content: center;
display: flex;
`
const IconStyle = css` const IconStyle = css`
height: 16px; height: 16px;
width: 18px; width: 18px;
margin-left: 10px; margin-left: 10px;
` `
const LinkIcon = styled(LinkIconFeather)` const LinkIcon = styled(ExternalLinkIconFeather)`
${IconStyle} ${IconStyle}
${ClickableStyle}
${LinkStyle} ${LinkStyle}
` `
const CopyIcon = styled(Copy)` const CopyIcon = styled(Copy)`
${IconStyle} ${IconStyle}
${ClickableStyle}
${LinkStyle} ${LinkStyle}
stroke: ${({ theme }) => theme.accentActive}; stroke: ${({ theme }) => theme.accentActive};
` `
...@@ -129,7 +143,7 @@ export const TrashIcon = styled(Trash)` ...@@ -129,7 +143,7 @@ export const TrashIcon = styled(Trash)`
display: flex; display: flex;
:hover { :hover {
opacity: 0.7; opacity: ${OPACITY_HOVER};
} }
` `
...@@ -169,6 +183,7 @@ function handleClickExternalLink(event: React.MouseEvent<HTMLAnchorElement>) { ...@@ -169,6 +183,7 @@ function handleClickExternalLink(event: React.MouseEvent<HTMLAnchorElement>) {
} }
const StyledLink = styled.a` const StyledLink = styled.a`
${ClickableStyle}
${LinkStyle} ${LinkStyle}
` `
/** /**
...@@ -227,6 +242,14 @@ function ToolTip() { ...@@ -227,6 +242,14 @@ function ToolTip() {
) )
} }
const CopyIconWrapper = styled.div`
text-decoration: none;
cursor: pointer;
align-items: center;
justify-content: center;
display: flex;
`
export function CopyLinkIcon({ toCopy }: { toCopy: string }) { export function CopyLinkIcon({ toCopy }: { toCopy: string }) {
const [isCopied, setCopied] = useCopyClipboard() const [isCopied, setCopied] = useCopyClipboard()
const copy = useCallback(() => { const copy = useCallback(() => {
...@@ -240,6 +263,134 @@ export function CopyLinkIcon({ toCopy }: { toCopy: string }) { ...@@ -240,6 +263,134 @@ export function CopyLinkIcon({ toCopy }: { toCopy: string }) {
) )
} }
const FullAddress = styled.span`
@media only screen and (max-width: ${MOBILE_MEDIA_BREAKPOINT}) {
display: none;
}
`
const TruncatedAddress = styled.span`
display: none;
@media only screen and (max-width: ${MOBILE_MEDIA_BREAKPOINT}) {
display: flex;
}
`
const CopyAddressRow = styled.div<{ isClicked: boolean }>`
${ClickableStyle}
color: inherit;
stroke: inherit;
cursor: pointer;
align-items: center;
justify-content: center;
display: flex;
gap: 6px;
${({ isClicked }) => isClicked && `opacity: ` + OPACITY_CLICK + ` !important`}
`
const CopyContractAddressWrapper = styled.div`
position: relative;
align-items: center;
justify-content: center;
display: flex;
`
export function CopyContractAddress({ address }: { address: string }) {
const [isCopied, setCopied] = useCopyClipboard()
const copy = useCallback(() => {
setCopied(address)
}, [address, setCopied])
const truncated = `${address.slice(0, 4)}...${address.slice(-3)}`
return (
<CopyContractAddressWrapper onClick={copy}>
<CopyAddressRow isClicked={isCopied}>
<FullAddress>{address}</FullAddress>
<TruncatedAddress>{truncated}</TruncatedAddress>
<Copy size={14} />
</CopyAddressRow>
{isCopied && <ToolTip />}
</CopyContractAddressWrapper>
)
}
const CopyHelperContainer = styled(LinkStyledButton)<{ clicked: boolean }>`
${({ clicked }) => !clicked && ClickableStyle};
color: ${({ color, theme }) => color || theme.accentAction};
padding: 0;
flex-shrink: 0;
display: flex;
text-decoration: none;
:hover,
:active,
:focus {
text-decoration: none;
color: ${({ color, theme }) => color || theme.accentAction};
}
`
const CopyHelperText = styled.span<{ fontSize: number }>`
${({ theme }) => theme.flexRowNoWrap};
font-size: ${({ fontSize }) => fontSize + 'px'};
font-weight: 400;
align-items: center;
`
const CopiedIcon = styled(CheckCircle)`
color: ${({ theme }) => theme.accentSuccess};
stroke-width: 1.5px;
`
interface CopyHelperProps {
link?: boolean
toCopy: string
color?: Color
fontSize?: number
iconSize?: number
gap?: number
iconPosition?: 'left' | 'right'
iconColor?: Color
children: ReactNode
}
export type CopyHelperRefType = { forceCopy: () => void }
export const CopyHelper = forwardRef<CopyHelperRefType, CopyHelperProps>(
(
{
link,
toCopy,
color,
fontSize = 16,
iconSize = 20,
gap = 12,
iconPosition = 'left',
iconColor,
children,
}: CopyHelperProps,
ref
) => {
const [isCopied, setCopied] = useCopyClipboard()
const copy = useCallback(() => {
setCopied(toCopy)
}, [toCopy, setCopied])
useImperativeHandle(ref, () => ({
forceCopy() {
copy()
},
}))
const BaseIcon = isCopied ? CopiedIcon : link ? LinkIconFeather : Copy
return (
<CopyHelperContainer onClick={copy} color={color} clicked={isCopied}>
<div style={{ display: 'flex', flexDirection: 'row', gap }}>
{iconPosition === 'left' && <BaseIcon size={iconSize} strokeWidth={1.5} color={iconColor} />}
<CopyHelperText fontSize={fontSize}>{isCopied ? <Trans>Copied!</Trans> : children}</CopyHelperText>
{iconPosition === 'right' && <BaseIcon size={iconSize} strokeWidth={1.5} color={iconColor} />}
</div>
</CopyHelperContainer>
)
}
)
CopyHelper.displayName = 'CopyHelper'
const rotate = keyframes` const rotate = keyframes`
from { from {
transform: rotate(0deg); transform: rotate(0deg);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment