From a1b3776686f87c5491e82198bd545bffc9e88d28 Mon Sep 17 00:00:00 2001 From: Jordan Frankfurt Date: Mon, 7 Aug 2023 14:22:45 -0500 Subject: [PATCH] feat: vertical LP (#7000) * Re-organize add liquidity page * fix title alignment * Update styles * Update logic for disable/enable input on creation * lint and clean up a couple small things * lint and clean up a couple small things * Tweak UI * clean up code * add back range selector * remove inline styles --------- Co-authored-by: Callil Capuozzo --- src/components/CurrencyInputPanel/index.tsx | 209 ++++----- .../InputStepCounter/InputStepCounter.tsx | 58 ++- .../LiquidityChartRangeInput/Zoom.tsx | 2 +- .../LiquidityChartRangeInput/index.tsx | 7 +- src/components/NavigationTabs/index.tsx | 53 +-- .../RangeSelector/PresetsButtons.tsx | 2 +- src/components/RangeSelector/index.tsx | 63 ++- src/pages/AddLiquidity/index.tsx | 421 +++++++++--------- src/pages/AddLiquidity/styled.tsx | 67 +-- src/pages/AppBody.tsx | 2 +- src/pages/CreateProposal/index.tsx | 21 +- 11 files changed, 400 insertions(+), 505 deletions(-) diff --git a/src/components/CurrencyInputPanel/index.tsx b/src/components/CurrencyInputPanel/index.tsx index 3ec169b600..91904adf5c 100644 --- a/src/components/CurrencyInputPanel/index.tsx +++ b/src/components/CurrencyInputPanel/index.tsx @@ -4,12 +4,10 @@ import { Currency, CurrencyAmount } from '@uniswap/sdk-core' import { Pair } from '@uniswap/v2-sdk' import { useWeb3React } from '@web3-react/core' import { TraceEvent } from 'analytics' -import { AutoColumn } from 'components/Column' import { LoadingOpacityContainer, loadingOpacityMixin } from 'components/Loader/styled' import { isSupportedChain } from 'constants/chains' import { darken } from 'polished' import { ReactNode, useCallback, useState } from 'react' -import { Lock } from 'react-feather' import styled, { useTheme } from 'styled-components/macro' import { flexColumnNoWrap, flexRowNoWrap } from 'theme/styles' import { formatCurrencyAmount } from 'utils/formatCurrencyAmount' @@ -36,18 +34,6 @@ const InputPanel = styled.div<{ hideInput?: boolean }>` will-change: height; ` -const FixedContainer = styled.div` - width: 100%; - height: 100%; - position: absolute; - border-radius: 16px; - background-color: ${({ theme }) => theme.backgroundInteractive}; - display: flex; - align-items: center; - justify-content: center; - z-index: 2; -` - const Container = styled.div<{ hideInput: boolean; disabled: boolean }>` border-radius: ${({ hideInput }) => (hideInput ? '16px' : '20px')}; border: 1px solid ${({ theme }) => theme.backgroundSurface}; @@ -226,110 +212,99 @@ export default function CurrencyInputPanel({ return ( - {locked && ( - - - - - The market price is outside your specified price range. Single-asset deposit only. - - - - )} - - - {!hideInput && ( - - )} - - { - if (onCurrencySelect) { - setModalOpen(true) - } - }} - pointerEvents={!onCurrencySelect ? 'none' : undefined} - > - - - {pair ? ( - - - - ) : currency ? ( - - ) : null} - {pair ? ( - - {pair?.token0.symbol}:{pair?.token1.symbol} - - ) : ( - - {(currency && currency.symbol && currency.symbol.length > 20 - ? currency.symbol.slice(0, 4) + - '...' + - currency.symbol.slice(currency.symbol.length - 5, currency.symbol.length) - : currency?.symbol) || Select a token} - - )} - - {onCurrencySelect && } - - - - {!hideInput && !hideBalance && currency && ( - - - - {fiatValue && } - - {account ? ( - - - {!hideBalance && currency && selectedCurrencyBalance ? ( - renderBalance ? ( - renderBalance(selectedCurrencyBalance) - ) : ( - Balance: {formatCurrencyAmount(selectedCurrencyBalance, 4)} - ) - ) : null} - - {showMaxButton && selectedCurrencyBalance ? ( - - - MAX - - - ) : null} - - ) : ( - + {!locked && ( + <> + + + {!hideInput && ( + )} - - - )} - + + { + if (onCurrencySelect) { + setModalOpen(true) + } + }} + pointerEvents={!onCurrencySelect ? 'none' : undefined} + > + + + {pair ? ( + + + + ) : ( + currency && + )} + {pair ? ( + + {pair?.token0.symbol}:{pair?.token1.symbol} + + ) : ( + + {(currency && currency.symbol && currency.symbol.length > 20 + ? currency.symbol.slice(0, 4) + + '...' + + currency.symbol.slice(currency.symbol.length - 5, currency.symbol.length) + : currency?.symbol) || Select a token} + + )} + + {onCurrencySelect && } + + + + {Boolean(!hideInput && !hideBalance && currency) && ( + + + + {fiatValue && } + + {account && ( + + + {Boolean(!hideBalance && currency && selectedCurrencyBalance) && + (renderBalance?.(selectedCurrencyBalance as CurrencyAmount) || ( + Balance: {formatCurrencyAmount(selectedCurrencyBalance, 4)} + ))} + + {Boolean(showMaxButton && selectedCurrencyBalance) && ( + + + MAX + + + )} + + )} + + + )} + + + )} {onCurrencySelect && ( keyframes` ` const InputRow = styled.div` - display: grid; - - grid-template-columns: 30px 1fr 30px; + display: flex; ` const SmallButton = styled(ButtonGray)` @@ -43,18 +41,17 @@ const FocusedOutlineCard = styled(OutlineCard)<{ active?: boolean; pulsing?: boo const StyledInput = styled(NumericalInput)<{ usePercent?: boolean }>` background-color: transparent; - text-align: center; - width: 100%; font-weight: 500; - padding: 0 10px; + text-align: left; + width: 100%; ${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall` font-size: 16px; `}; +` - ${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToExtraSmall` - font-size: 12px; - `}; +const InputColumn = styled(AutoColumn)` + width: 100%; ` const InputTitle = styled(ThemedText.DeprecatedSmall)` @@ -142,20 +139,11 @@ const StepCounter = ({ return ( - - - {title} - - - - {!locked && ( - - - - - - )} - + + + + {title} + + + + {tokenB} per {tokenA} + + + + {!locked && ( @@ -173,14 +168,15 @@ const StepCounter = ({ )} - - - - - {tokenB} per {tokenA} - - - + {!locked && ( + + + + + + )} + + ) } diff --git a/src/components/LiquidityChartRangeInput/Zoom.tsx b/src/components/LiquidityChartRangeInput/Zoom.tsx index e0e257635f..d8cc6f8cd4 100644 --- a/src/components/LiquidityChartRangeInput/Zoom.tsx +++ b/src/components/LiquidityChartRangeInput/Zoom.tsx @@ -12,7 +12,7 @@ const Wrapper = styled.div<{ count: number }>` grid-gap: 6px; position: absolute; - top: -75px; + top: -32px; right: 0; ` diff --git a/src/components/LiquidityChartRangeInput/index.tsx b/src/components/LiquidityChartRangeInput/index.tsx index 59cb11ba31..beeba91eb7 100644 --- a/src/components/LiquidityChartRangeInput/index.tsx +++ b/src/components/LiquidityChartRangeInput/index.tsx @@ -6,7 +6,7 @@ import Loader from 'components/Icons/LoadingSpinner' import { format } from 'd3' import { useColor } from 'hooks/useColor' import { saturate } from 'polished' -import React, { ReactNode, useCallback, useMemo } from 'react' +import { ReactNode, useCallback, useMemo } from 'react' import { BarChart2, CloudOff, Inbox } from 'react-feather' import { batch } from 'react-redux' import { Bound } from 'state/mint/v3/actions' @@ -46,7 +46,8 @@ const ZOOM_LEVELS: Record = { const ChartWrapper = styled.div` position: relative; - + width: 100%; + max-height: 200px; justify-content: center; align-content: center; ` @@ -180,7 +181,7 @@ export default function LiquidityChartRangeInput({ ` +const StyledLink = styled(Link)<{ flex?: string }>` flex: ${({ flex }) => flex ?? 'none'}; ${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium` @@ -31,9 +31,10 @@ const StyledHistoryLink = styled(HistoryLink)<{ flex?: string }>` `}; ` -const ActiveText = styled.div` - font-weight: 500; - font-size: 20px; +const FindPoolTabsText = styled(ThemedText.SubHeaderLarge)` + position: absolute; + left: 50%; + transform: translateX(-50%); ` const StyledArrowLeft = styled(ArrowLeft)` @@ -44,17 +45,22 @@ export function FindPoolTabs({ origin }: { origin: string }) { return ( - + - - + + Import V2 Pool - + ) } +const AddRemoveTitleText = styled(ThemedText.SubHeaderLarge)` + flex: 1; + margin: auto; +` + export function AddRemoveTabs({ adding, creating, @@ -83,7 +89,7 @@ export function AddRemoveTabs({ return ( - { if (adding) { @@ -95,12 +101,8 @@ export function AddRemoveTabs({ flex={children ? '1' : undefined} > - - + + {creating ? ( Create a pair ) : adding ? ( @@ -108,23 +110,10 @@ export function AddRemoveTabs({ ) : ( Remove Liquidity )} - - {children} + + {children && {children}} ) } - -export function CreateProposalTabs() { - return ( - - - - - - Create Proposal - - - ) -} diff --git a/src/components/RangeSelector/PresetsButtons.tsx b/src/components/RangeSelector/PresetsButtons.tsx index 25baf63ba6..96c6f7515b 100644 --- a/src/components/RangeSelector/PresetsButtons.tsx +++ b/src/components/RangeSelector/PresetsButtons.tsx @@ -5,7 +5,7 @@ import styled from 'styled-components/macro' import { ThemedText } from 'theme' const Button = styled(ButtonOutlined).attrs(() => ({ - padding: '8px', + padding: '6px', $borderRadius: '8px', }))` color: ${({ theme }) => theme.textPrimary}; diff --git a/src/components/RangeSelector/index.tsx b/src/components/RangeSelector/index.tsx index 24700b8300..17a11e4f26 100644 --- a/src/components/RangeSelector/index.tsx +++ b/src/components/RangeSelector/index.tsx @@ -1,8 +1,7 @@ import { Trans } from '@lingui/macro' import { Currency, Price, Token } from '@uniswap/sdk-core' -import { AutoColumn } from 'components/Column' import StepCounter from 'components/InputStepCounter/InputStepCounter' -import { RowBetween } from 'components/Row' +import { AutoRow } from 'components/Row' import { Bound } from 'state/mint/v3/actions' // currencyA is the base token @@ -41,37 +40,33 @@ export default function RangeSelector({ const rightPrice = isSorted ? priceUpper : priceLower?.invert() return ( - - - Min Price} - tokenA={currencyA?.symbol} - tokenB={currencyB?.symbol} - /> - Max Price} - /> - - + + Low price} + tokenA={currencyA?.symbol} + tokenB={currencyB?.symbol} + /> + High price} + /> + ) } diff --git a/src/pages/AddLiquidity/index.tsx b/src/pages/AddLiquidity/index.tsx index c772d9dba0..62bd2a0693 100644 --- a/src/pages/AddLiquidity/index.tsx +++ b/src/pages/AddLiquidity/index.tsx @@ -13,6 +13,7 @@ import UnsupportedCurrencyFooter from 'components/swap/UnsupportedCurrencyFooter import { isSupportedChain } from 'constants/chains' import usePrevious from 'hooks/usePrevious' import { useSingleCallResult } from 'lib/hooks/multicall' +import { BodyWrapper } from 'pages/AppBody' import { PositionPageUnsupportedContent } from 'pages/Pool/PositionPage' import { useCallback, useEffect, useMemo, useState } from 'react' import { AlertTriangle } from 'react-feather' @@ -24,7 +25,7 @@ import { useV3MintActionHandlers, useV3MintState, } from 'state/mint/v3/hooks' -import { useTheme } from 'styled-components/macro' +import styled, { useTheme } from 'styled-components/macro' import { addressesAreEquivalent } from 'utils/addressesAreEquivalent' import { ButtonError, ButtonLight, ButtonPrimary, ButtonText } from '../../components/Button' @@ -39,7 +40,7 @@ import { PositionPreview } from '../../components/PositionPreview' import RangeSelector from '../../components/RangeSelector' import PresetsButtons from '../../components/RangeSelector/PresetsButtons' import RateToggle from '../../components/RateToggle' -import Row, { AutoRow, RowBetween, RowFixed } from '../../components/Row' +import Row, { RowBetween, RowFixed } from '../../components/Row' import { SwitchLocaleLink } from '../../components/SwitchLocaleLink' import TransactionConfirmationModal, { ConfirmationModalContent } from '../../components/TransactionConfirmationModal' import { ZERO_PERCENT } from '../../constants/misc' @@ -67,20 +68,20 @@ import { Review } from './Review' import { CurrencyDropdown, DynamicSection, - HideMedium, MediumOnly, - PageWrapper, ResponsiveTwoColumns, - RightContainer, ScrollablePage, - StackedContainer, - StackedItem, StyledInput, Wrapper, } from './styled' const DEFAULT_ADD_IN_RANGE_SLIPPAGE_TOLERANCE = new Percent(50, 10_000) +const StyledBodyWrapper = styled(BodyWrapper)<{ $hasExistingPosition: boolean }>` + padding: ${({ $hasExistingPosition }) => ($hasExistingPosition ? '10px' : 0)}; + max-width: 640px; +` + export default function AddLiquidityWrapper() { const { chainId } = useWeb3React() if (isSupportedChain(chainId)) { @@ -97,7 +98,12 @@ function AddLiquidity() { currencyIdB, feeAmount: feeAmountFromUrl, tokenId, - } = useParams<{ currencyIdA?: string; currencyIdB?: string; feeAmount?: string; tokenId?: string }>() + } = useParams<{ + currencyIdA?: string + currencyIdB?: string + feeAmount?: string + tokenId?: string + }>() const { account, chainId, provider } = useWeb3React() const theme = useTheme() @@ -600,7 +606,7 @@ function AddLiquidity() { )} pendingText={pendingText} /> - + - + Clear All - {baseCurrency && quoteCurrency ? ( - { - if (!ticksAtLimit[Bound.LOWER] && !ticksAtLimit[Bound.UPPER]) { - onLeftRangeInput((invertPrice ? priceLower : priceUpper?.invert())?.toSignificant(6) ?? '') - onRightRangeInput((invertPrice ? priceUpper : priceLower?.invert())?.toSignificant(6) ?? '') - onFieldAInput(formattedAmounts[Field.CURRENCY_B] ?? '') - } - navigate( - `/add/${currencyIdB as string}/${currencyIdA as string}${feeAmount ? '/' + feeAmount : ''}` - ) - }} - /> - ) : null} )} @@ -698,10 +688,190 @@ function AddLiquidity() { /> )} + + {!hasExistingPosition && ( + <> + + + + Set Price Range + + + {Boolean(baseCurrency && quoteCurrency) && ( + + + { + if (!ticksAtLimit[Bound.LOWER] && !ticksAtLimit[Bound.UPPER]) { + onLeftRangeInput( + (invertPrice ? priceLower : priceUpper?.invert())?.toSignificant(6) ?? '' + ) + onRightRangeInput( + (invertPrice ? priceUpper : priceLower?.invert())?.toSignificant(6) ?? '' + ) + onFieldAInput(formattedAmounts[Field.CURRENCY_B] ?? '') + } + navigate( + `/add/${currencyIdB as string}/${currencyIdA as string}${ + feeAmount ? '/' + feeAmount : '' + }` + ) + }} + /> + + )} + + + + + {outOfRange && ( + + + + + + Your position will not earn fees or be used in trades until the market price moves into + your range. + + + + + )} + + {invalidRange && ( + + + + + Invalid range selected. The min price must be lower than the max price. + + + + )} + + + + {!noLiquidity ? ( + <> + {Boolean(price && baseCurrency && quoteCurrency && !noLiquidity) && ( + + + + Current Price: + + + {price && ( + + )} + + {baseCurrency && ( + + {quoteCurrency?.symbol} per {baseCurrency.symbol} + + )} + + + )} + + + ) : ( + + {noLiquidity && ( + + + + This pool must be initialized before you can add liquidity. To initialize, select a + starting price for the pool. Then, enter your liquidity price range and deposit amount. + Gas fees will be higher than usual due to the initialization transaction. + + + + )} + + + + + + Starting {baseCurrency?.symbol} Price: + + + {price ? ( + + + {' '} + + {quoteCurrency?.symbol} per {baseCurrency?.symbol} + + + + ) : ( + '-' + )} + + + + )} + + + )}
- + {hasExistingPosition ? Add more liquidity : Deposit Amounts} @@ -737,199 +907,10 @@ function AddLiquidity() {
- - {!hasExistingPosition ? ( - <> - - - - - - {!noLiquidity ? ( - <> - - - Set Price Range - - - - {price && baseCurrency && quoteCurrency && !noLiquidity && ( - - - - Current Price: - - - - - - {quoteCurrency?.symbol} per {baseCurrency.symbol} - - - - )} - - - - ) : ( - - - - Set Starting Price - - - {noLiquidity && ( - - - - This pool must be initialized before you can add liquidity. To initialize, select a - starting price for the pool. Then, enter your liquidity price range and deposit - amount. Gas fees will be higher than usual due to the initialization transaction. - - - - )} - - - - - - Current {baseCurrency?.symbol} Price: - - - {price ? ( - - - {' '} - {quoteCurrency?.symbol} - - - ) : ( - '-' - )} - - - - )} - - - - - - - {noLiquidity && ( - - - Set Price Range - - - )} - - - - - - - {outOfRange ? ( - - - - - - Your position will not earn fees or be used in trades until the market price moves into - your range. - - - - - ) : null} - - {invalidRange ? ( - - - - - Invalid range selected. The min price must be lower than the max price. - - - - ) : null} - - - - - - - - ) : ( - - )} + -
+ {showOwnershipWarning && } {addIsUnsupported && ( ` - max-width: ${({ wide }) => (wide ? '880px' : '480px')}; - width: 100%; - - padding: ${({ wide }) => (wide ? '10px' : '0')}; - - ${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium` - max-width: 480px; - `}; -` - export const Wrapper = styled.div` position: relative; padding: 26px 16px; - min-width: 480px; - - ${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium` - min-width: 400px; - `}; - - ${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToExtraSmall` - min-width: 340px; -`}; ` export const ScrollablePage = styled.div` - padding: 68px 8px 0px; + padding: 20px 8px 0px; position: relative; display: flex; flex-direction: column; ${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium` - max-width: 480px; margin: 0 auto; `}; @@ -67,56 +45,19 @@ export const StyledInput = styled(Input)` /* two-column layout where DepositAmount is moved at the very end on mobile. */ export const ResponsiveTwoColumns = styled.div<{ wide: boolean }>` - display: grid; - grid-column-gap: 50px; - grid-row-gap: 15px; - grid-template-columns: ${({ wide }) => (wide ? '1fr 1fr' : '1fr')}; - grid-template-rows: max-content; - grid-auto-flow: row; - + display: flex; + flex-direction: column; + gap: 20px; padding-top: 20px; - border-top: 1px solid ${({ theme }) => theme.backgroundInteractive}; ${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium` - grid-template-columns: 1fr; - margin-top: 0; `}; ` -export const RightContainer = styled(AutoColumn)` - grid-row: 1 / 3; - grid-column: 2; - height: fit-content; - - ${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium` - grid-row: 2 / 3; - grid-column: 1; - `}; -` - -export const StackedContainer = styled.div` - display: grid; -` - -export const StackedItem = styled.div<{ zIndex?: number }>` - grid-column: 1; - grid-row: 1; - height: 100%; - z-index: ${({ zIndex }) => zIndex}; -` - export const MediumOnly = styled.div` ${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium` display: none; `}; ` - -export const HideMedium = styled.div` - display: none; - - ${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium` - display: block; - `}; -` diff --git a/src/pages/AppBody.tsx b/src/pages/AppBody.tsx index d5fd81467d..8b7843c91d 100644 --- a/src/pages/AppBody.tsx +++ b/src/pages/AppBody.tsx @@ -1,4 +1,4 @@ -import React, { PropsWithChildren } from 'react' +import { PropsWithChildren } from 'react' import styled from 'styled-components/macro' import { Z_INDEX } from 'theme/zIndex' diff --git a/src/pages/CreateProposal/index.tsx b/src/pages/CreateProposal/index.tsx index 343170c988..44bd976f9a 100644 --- a/src/pages/CreateProposal/index.tsx +++ b/src/pages/CreateProposal/index.tsx @@ -12,6 +12,8 @@ import JSBI from 'jsbi' import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount' import { Wrapper } from 'pages/Pool/styleds' import { useCallback, useMemo, useState } from 'react' +import { ArrowLeft } from 'react-feather' +import { Link } from 'react-router-dom' import { CreateProposalData, ProposalState, @@ -24,7 +26,6 @@ import { import styled from 'styled-components/macro' import { ExternalLink, ThemedText } from 'theme' -import { CreateProposalTabs } from '../../components/NavigationTabs' import { LATEST_GOVERNOR_INDEX } from '../../constants/governance' import { UNI } from '../../constants/tokens' import AppBody from '../AppBody' @@ -45,6 +46,19 @@ const PageWrapper = styled(AutoColumn)` } ` +const BackArrow = styled(ArrowLeft)` + cursor: pointer; + color: ${({ theme }) => theme.textPrimary}; +` +const Nav = styled(Link)` + align-items: center; + display: flex; + flex-direction: row; + justify-content: flex-start; + margin: 1em 0 0 1em; + text-decoration: none; +` + const CreateProposalButton = ({ proposalThreshold, hasActiveOrPendingProposal, @@ -242,7 +256,10 @@ ${bodyValue} - +