feat: polish select (#3160)

* feat: filter selected currency from select

* test: use infura urls

* fix: load native with chain

* fix: use currencyId for key

* feat: switch currencies when selecting other

* fix: resolve merge conflict name
This commit is contained in:
Zach Pomerantz 2022-01-20 16:15:23 -08:00 committed by GitHub
parent 567fb0181c
commit b501974a76
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 67 additions and 55 deletions

@ -20,6 +20,7 @@ import {
import AutoSizer from 'react-virtualized-auto-sizer' import AutoSizer from 'react-virtualized-auto-sizer'
import { areEqual, FixedSizeList, FixedSizeListProps } from 'react-window' import { areEqual, FixedSizeList, FixedSizeListProps } from 'react-window'
import invariant from 'tiny-invariant' import invariant from 'tiny-invariant'
import { currencyId } from 'utils/currencyId'
import { BaseButton } from '../Button' import { BaseButton } from '../Button'
import Column from '../Column' import Column from '../Column'
@ -107,10 +108,7 @@ function TokenOption({ index, value, style }: TokenOptionProps) {
) )
} }
const itemKey = (index: number, tokens: ItemData) => { const itemKey = (index: number, tokens: ItemData) => currencyId(tokens[index])
if (tokens[index].isNative) return 'native'
return tokens[index].wrapped.address
}
const ItemRow = memo(function ItemRow({ const ItemRow = memo(function ItemRow({
data: tokens, data: tokens,
index, index,

@ -2,7 +2,8 @@ import { t, Trans } from '@lingui/macro'
import { Currency } from '@uniswap/sdk-core' import { Currency } from '@uniswap/sdk-core'
import { useQueryTokenList } from 'lib/hooks/useTokenList' import { useQueryTokenList } from 'lib/hooks/useTokenList'
import styled, { ThemedText } from 'lib/theme' import styled, { ThemedText } from 'lib/theme'
import { ElementRef, useCallback, useEffect, useRef, useState } from 'react' import { ElementRef, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { currencyId } from 'utils/currencyId'
import Column from '../Column' import Column from '../Column'
import Dialog, { Header } from '../Dialog' import Dialog, { Header } from '../Dialog'
@ -17,14 +18,18 @@ const SearchInput = styled(StringInput)`
${inputCss} ${inputCss}
` `
export function TokenSelectDialog({ onSelect }: { onSelect: (token: Currency) => void }) { interface TokenSelectDialogProps {
value?: Currency
onSelect: (token: Currency) => void
}
export function TokenSelectDialog({ value, onSelect }: TokenSelectDialogProps) {
const [query, setQuery] = useState('') const [query, setQuery] = useState('')
const tokens = useQueryTokenList(query) const queriedTokens = useQueryTokenList(query)
const tokens = useMemo(() => queriedTokens.filter((token) => token !== value), [queriedTokens, value])
const baseTokens: Currency[] = [] // TODO(zzmp): Add base tokens to token list functionality const baseTokens: Currency[] = [] // TODO(zzmp): Add base tokens to token list functionality
// TODO(zzmp): Disable already selected tokens (passed as props?)
const input = useRef<HTMLInputElement>(null) const input = useRef<HTMLInputElement>(null)
useEffect(() => input.current?.focus(), [input]) useEffect(() => input.current?.focus(), [input])
@ -49,7 +54,7 @@ export function TokenSelectDialog({ onSelect }: { onSelect: (token: Currency) =>
{Boolean(baseTokens.length) && ( {Boolean(baseTokens.length) && (
<Row pad={0.75} gap={0.25} justify="flex-start" flex> <Row pad={0.75} gap={0.25} justify="flex-start" flex>
{baseTokens.map((token) => ( {baseTokens.map((token) => (
<TokenBase value={token} onClick={onSelect} key={token.wrapped.address} /> <TokenBase value={token} onClick={onSelect} key={currencyId(token)} />
))} ))}
</Row> </Row>
)} )}
@ -81,7 +86,7 @@ export default function TokenSelect({ value, collapsed, disabled, onSelect }: To
<TokenButton value={value} collapsed={collapsed} disabled={disabled} onClick={() => setOpen(true)} /> <TokenButton value={value} collapsed={collapsed} disabled={disabled} onClick={() => setOpen(true)} />
{open && ( {open && (
<Dialog color="module" onClose={() => setOpen(false)}> <Dialog color="module" onClose={() => setOpen(false)}>
<TokenSelectDialog onSelect={selectAndClose} /> <TokenSelectDialog value={value} onSelect={selectAndClose} />
</Dialog> </Dialog>
)} )}
</> </>

@ -1,12 +1,14 @@
import { INFURA_NETWORK_URLS } from 'constants/chainInfo'
import { SupportedChainId } from 'constants/chains' import { SupportedChainId } from 'constants/chains'
import { DEFAULT_LOCALE, SUPPORTED_LOCALES } from 'constants/locales' import { DEFAULT_LOCALE, SUPPORTED_LOCALES } from 'constants/locales'
import Widget from 'lib/components/Widget' import Widget from 'lib/components/Widget'
import { darkTheme, defaultTheme, lightTheme } from 'lib/theme' import { darkTheme, defaultTheme, lightTheme } from 'lib/theme'
import { ReactNode, useEffect, useMemo } from 'react' import { ReactNode, useEffect, useMemo } from 'react'
import { useSelect, useValue } from 'react-cosmos/fixture' import { useSelect, useValue } from 'react-cosmos/fixture'
import { initializeConnector } from 'widgets-web3-react/core'
import { MetaMask } from 'widgets-web3-react/metamask'
import { metaMask } from '../connectors/metaMask' export const [metaMask] = initializeConnector<MetaMask>((actions) => new MetaMask(actions))
import { URLS } from '../connectors/network'
export default function Wrapper({ children }: { children: ReactNode }) { export default function Wrapper({ children }: { children: ReactNode }) {
const [width] = useValue('width', { defaultValue: 360 }) const [width] = useValue('width', { defaultValue: 360 })
@ -21,8 +23,8 @@ export default function Wrapper({ children }: { children: ReactNode }) {
const NO_JSON_RPC = 'None' const NO_JSON_RPC = 'None'
const [jsonRpcEndpoint] = useSelect('JSON-RPC', { const [jsonRpcEndpoint] = useSelect('JSON-RPC', {
defaultValue: URLS[SupportedChainId.MAINNET][0] || NO_JSON_RPC, defaultValue: INFURA_NETWORK_URLS[SupportedChainId.MAINNET],
options: [NO_JSON_RPC, ...Object.values(URLS).flat()], options: [NO_JSON_RPC, ...Object.values(INFURA_NETWORK_URLS).sort()],
}) })
const NO_PROVIDER = 'None' const NO_PROVIDER = 'None'

@ -1,4 +0,0 @@
import { initializeConnector } from 'widgets-web3-react/core'
import { MetaMask } from 'widgets-web3-react/metamask'
export const [metaMask, hooks] = initializeConnector<MetaMask>((actions) => new MetaMask(actions))

@ -1,14 +0,0 @@
import { SupportedChainId } from 'constants/chains'
const ALCHEMY_KEY = '-mzwnEVG3Ssm75WVbmsEpYiekfTF3W1z'
const alchemyUrl = (network: string) => `https://${network}.alchemyapi.io/v2/${ALCHEMY_KEY}`
export const URLS = {
[SupportedChainId.MAINNET]: [alchemyUrl('eth-mainnet')],
[SupportedChainId.ROPSTEN]: [alchemyUrl('eth-ropsten')],
[SupportedChainId.RINKEBY]: [alchemyUrl('eth-rinkeby')],
[SupportedChainId.GOERLI]: [alchemyUrl('eth-goerli')],
[SupportedChainId.KOVAN]: [alchemyUrl('eth-kovan')],
[SupportedChainId.OPTIMISM]: [alchemyUrl('optimism-mainnet')],
[SupportedChainId.ARBITRUM_ONE]: [alchemyUrl('arbitrum-mainnet')],
}

@ -6,25 +6,15 @@ import { amountAtom, Field, independentFieldAtom, swapAtom } from 'lib/state/swa
import { useCallback, useMemo } from 'react' import { useCallback, useMemo } from 'react'
export { default as useSwapInfo } from './useSwapInfo' export { default as useSwapInfo } from './useSwapInfo'
export function useSwapCurrency(field: Field): [Currency | undefined, (currency?: Currency) => void] { function otherField(field: Field) {
const atom = useMemo(() => pickAtom(swapAtom, field), [field]) switch (field) {
return useAtom(atom) case Field.INPUT:
return Field.OUTPUT
break
case Field.OUTPUT:
return Field.INPUT
break
} }
export function useSwapAmount(field: Field): [string | undefined, (amount: string) => void] {
const amount = useAtomValue(amountAtom)
const independentField = useAtomValue(independentFieldAtom)
const value = useMemo(() => (independentField === field ? amount : undefined), [amount, independentField, field])
const updateSwap = useUpdateAtom(swapAtom)
const updateAmount = useCallback(
(amount: string) =>
updateSwap((swap) => {
swap.independentField = field
swap.amount = amount
}),
[field, updateSwap]
)
return [value, updateAmount]
} }
export function useSwitchSwapCurrencies() { export function useSwitchSwapCurrencies() {
@ -45,3 +35,38 @@ export function useSwitchSwapCurrencies() {
}) })
}, [update]) }, [update])
} }
export function useSwapCurrency(field: Field): [Currency | undefined, (currency?: Currency) => void] {
const atom = useMemo(() => pickAtom(swapAtom, field), [field])
const otherAtom = useMemo(() => pickAtom(swapAtom, otherField(field)), [field])
const [currency, setCurrency] = useAtom(atom)
const otherCurrency = useAtomValue(otherAtom)
const switchSwapCurrencies = useSwitchSwapCurrencies()
const setOrSwitchCurrency = useCallback(
(currency?: Currency) => {
if (currency === otherCurrency) {
switchSwapCurrencies()
} else {
setCurrency(currency)
}
},
[otherCurrency, setCurrency, switchSwapCurrencies]
)
return [currency, setOrSwitchCurrency]
}
export function useSwapAmount(field: Field): [string | undefined, (amount: string) => void] {
const amount = useAtomValue(amountAtom)
const independentField = useAtomValue(independentFieldAtom)
const value = useMemo(() => (independentField === field ? amount : undefined), [amount, independentField, field])
const updateSwap = useUpdateAtom(swapAtom)
const updateAmount = useCallback(
(amount: string) =>
updateSwap((swap) => {
swap.independentField = field
swap.amount = amount
}),
[field, updateSwap]
)
return [value, updateAmount]
}

@ -1,7 +1,7 @@
import { nativeOnChain } from 'constants/tokens'
import useDebounce from 'hooks/useDebounce' import useDebounce from 'hooks/useDebounce'
import useActiveWeb3React from 'lib/hooks/useActiveWeb3React' import useActiveWeb3React from 'lib/hooks/useActiveWeb3React'
import { useTokenBalances } from 'lib/hooks/useCurrencyBalance' import { useTokenBalances } from 'lib/hooks/useCurrencyBalance'
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
import { useMemo } from 'react' import { useMemo } from 'react'
import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo' import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo'
@ -9,7 +9,7 @@ import { getTokenFilter } from './filtering'
import { tokenComparator, useSortTokensByQuery } from './sorting' import { tokenComparator, useSortTokensByQuery } from './sorting'
export function useQueryTokens(query: string, tokens: WrappedTokenInfo[]) { export function useQueryTokens(query: string, tokens: WrappedTokenInfo[]) {
const { account } = useActiveWeb3React() const { chainId, account } = useActiveWeb3React()
const balances = useTokenBalances(account, tokens) const balances = useTokenBalances(account, tokens)
const sortedTokens = useMemo( const sortedTokens = useMemo(
// Create a new array because sort is in-place and returns a referentially equivalent array. // Create a new array because sort is in-place and returns a referentially equivalent array.
@ -23,7 +23,7 @@ export function useQueryTokens(query: string, tokens: WrappedTokenInfo[]) {
const queriedTokens = useSortTokensByQuery(debouncedQuery, filteredTokens) const queriedTokens = useSortTokensByQuery(debouncedQuery, filteredTokens)
const native = useNativeCurrency() const native = useMemo(() => chainId && nativeOnChain(chainId), [chainId])
return useMemo(() => { return useMemo(() => {
if (native && filter(native)) { if (native && filter(native)) {
return [native, ...queriedTokens] return [native, ...queriedTokens]