Skip to content

Commit

Permalink
add basic keyboard navigation
Browse files Browse the repository at this point in the history
  • Loading branch information
JFrankfurt committed Jul 15, 2020
1 parent 9142be6 commit 4c0141d
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 66 deletions.
167 changes: 101 additions & 66 deletions src/components/Search/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import { useMedia } from 'react-use'
import { useAllPairsInUniswap, useAllTokensInUniswap } from '../../contexts/GlobalData'
import { OVERVIEW_TOKEN_BLACKLIST, OVERVIEW_PAIR_BLACKLIST } from '../../constants'

import { useKeyPress } from '../../hooks'

const Wrapper = styled.div`
display: flex;
position: relative;
Expand Down Expand Up @@ -68,7 +70,7 @@ const Menu = styled.div`
width: 100%;
top: 50px;
max-height: 540px;
overflow: scroll;
overflow-y: scroll;
left: 0;
padding-bottom: 20px;
background: white;
Expand All @@ -89,6 +91,10 @@ const MenuItem = styled(Row)`
cursor: pointer;
background-color: #f7f8fa;
}
:focus {
background-color: #f7f8fa;
outline: black auto 1px;
}
`

const Heading = styled(Row)`
Expand Down Expand Up @@ -119,7 +125,13 @@ export const Search = ({ small = false }) => {
const allPairs = useAllPairsInUniswap()
const allPairData = useAllPairData()

const [showMenu, toggleMenu] = useState(false)
const [cursor, setCursor] = useState(null)
const downKeyPressed = useKeyPress('ArrowDown')
const escapeKeyPressed = useKeyPress('Escape')
const tabKeyPressed = useKeyPress('Tab')
const upKeyPressed = useKeyPress('ArrowUp')

const [showMenu, setShowMenu] = useState(false)
const [value, setValue] = useState('')
const [, toggleShadow] = useState(false)
const [, toggleBottomShadow] = useState(false)
Expand All @@ -134,9 +146,9 @@ export const Search = ({ small = false }) => {

useEffect(() => {
if (value !== '') {
toggleMenu(true)
setShowMenu(true)
} else {
toggleMenu(false)
setShowMenu(false)
}
}, [value])

Expand Down Expand Up @@ -278,10 +290,50 @@ export const Search = ({ small = false }) => {
setPairsShown(Math.min(Object.keys(filteredPairList).length, 3))
}, [filteredPairList])

useEffect(() => {
if (pairsShown + tokensShown === 0) {
setCursor(undefined)
} else if (showMenu && downKeyPressed && cursor === undefined) {
setCursor(0)
} else if (showMenu && downKeyPressed && cursor < pairsShown + tokensShown) {
setCursor(cursor + 1)
}
}, [showMenu, downKeyPressed])

useEffect(() => {
if (pairsShown + tokensShown === 0) {
setCursor(undefined)
} else if (showMenu && upKeyPressed && cursor === undefined) {
setCursor(pairsShown + tokensShown - 1)
} else if (showMenu && upKeyPressed && cursor === 0) {
setCursor(pairsShown + tokensShown - 1)
} else if (showMenu && upKeyPressed && cursor > 0) {
setCursor(cursor - 1)
}
}, [showMenu, upKeyPressed])

useEffect(() => {
setCursor(undefined)
}, [tabKeyPressed])

useEffect(() => {
setCursor(undefined)
setShowMenu(false)
}, [escapeKeyPressed])

useEffect(() => {
const canUseCursor = Boolean(
Number.isInteger(cursor) && menuRef.current && menuRef.current.querySelectorAll('a')[cursor]
)
if (canUseCursor) {
menuRef.current.querySelectorAll('a')[cursor].focus()
}
}, [cursor])

function onDismiss() {
setPairsShown(3)
setTokensShown(3)
toggleMenu(false)
setShowMenu(false)
setValue('')
}

Expand All @@ -296,7 +348,7 @@ export const Search = ({ small = false }) => {
) {
setPairsShown(3)
setTokensShown(3)
toggleMenu(false)
setShowMenu(false)
}
}

Expand Down Expand Up @@ -338,7 +390,7 @@ export const Search = ({ small = false }) => {
setValue(e.target.value)
}}
onFocus={() => {
toggleMenu(true)
setShowMenu(true)
}}
/>
</Wrapper>
Expand All @@ -350,68 +402,51 @@ export const Search = ({ small = false }) => {
<Heading>
<Gray>Pairs</Gray>
</Heading>
<div>
{filteredPairList && Object.keys(filteredPairList).length === 0 && <MenuItem>No results</MenuItem>}
{filteredPairList &&
filteredPairList.slice(0, pairsShown).map(pair => {
if (pair?.token0?.id === '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2') {
pair.token0.name = 'ETH (Wrapped)'
pair.token0.symbol = 'ETH'
}
if (pair?.token1.id === '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2') {
pair.token1.name = 'ETH (Wrapped)'
pair.token1.symbol = 'ETH'
}
return (
<BasicLink to={'/pair/' + pair.id} key={pair.id} onClick={onDismiss}>
<MenuItem>
<DoubleTokenLogo a0={pair?.token0?.id} a1={pair?.token1?.id} margin={true} />
<span style={{ marginLeft: '10px' }}>{pair.token0.symbol + '-' + pair.token1.symbol} Pair</span>
</MenuItem>
</BasicLink>
)
})}
<Heading
hide={!(Object.keys(filteredPairList).length > 3 && Object.keys(filteredPairList).length >= pairsShown)}
>
<Blue
onClick={() => {
setPairsShown(pairsShown + 5)
}}
>
See more...
</Blue>
</Heading>
</div>
{Object.keys(filteredPairList).length === 0 && <MenuItem>No results</MenuItem>}
{filteredPairList.slice(0, pairsShown).map(pair => {
if (pair?.token0?.id === '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2') {
pair.token0.name = 'ETH (Wrapped)'
pair.token0.symbol = 'ETH'
}
if (pair?.token1.id === '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2') {
pair.token1.name = 'ETH (Wrapped)'
pair.token1.symbol = 'ETH'
}
return (
<BasicLink to={'/pair/' + pair.id} key={pair.id} onClick={onDismiss}>
<MenuItem>
<DoubleTokenLogo a0={pair?.token0?.id} a1={pair?.token1?.id} margin={true} />
<span style={{ marginLeft: '10px' }}>{pair.token0.symbol + '-' + pair.token1.symbol} Pair</span>
</MenuItem>
</BasicLink>
)
})}
<Heading
hide={!(Object.keys(filteredPairList).length > 3 && Object.keys(filteredPairList).length >= pairsShown)}
>
<Blue onClick={() => setPairsShown(pairsShown + 5)}>See more...</Blue>
</Heading>
<Heading>
<Gray>Tokens</Gray>
</Heading>
<div>
{Object.keys(filteredTokenList).length === 0 && <MenuItem>No results</MenuItem>}
{filteredTokenList.slice(0, tokensShown).map(token => {
return (
<BasicLink to={'/token/' + token.id} key={token.id} onClick={onDismiss}>
<MenuItem>
<TokenLogo address={token.id} style={{ marginRight: '10px' }} />
<span>{token.name}</span>
<span>({token.symbol})</span>
</MenuItem>
</BasicLink>
)
})}

<Heading
hide={!(Object.keys(filteredTokenList).length > 3 && Object.keys(filteredTokenList).length >= tokensShown)}
>
<Blue
onClick={() => {
setTokensShown(tokensShown + 5)
}}
>
See more...
</Blue>
</Heading>
</div>
{Object.keys(filteredTokenList).length === 0 && <MenuItem>No results</MenuItem>}
{filteredTokenList.slice(0, tokensShown).map(token => {
return (
<BasicLink to={'/token/' + token.id} key={token.id} onClick={onDismiss}>
<MenuItem>
<TokenLogo address={token.id} style={{ marginRight: '10px' }} />
<span>{token.name}</span>
<span>({token.symbol})</span>
</MenuItem>
</BasicLink>
)
})}

<Heading
hide={!(Object.keys(filteredTokenList).length > 3 && Object.keys(filteredTokenList).length >= tokensShown)}
>
<Blue onClick={() => setTokensShown(tokensShown + 5)}>See more...</Blue>
</Heading>
</Menu>
</div>
)
Expand Down
25 changes: 25 additions & 0 deletions src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,31 @@ export function useCopyClipboard(timeout = 500) {
return [isCopied, staticCopy]
}

export function useKeyPress(targetKey: string) {
const [keyPressed, setKeyPressed] = useState(false)

const downHandler = useRef(undefined)
downHandler.current = ({ key }) => {
if (key === targetKey) setKeyPressed(true)
}

const upHandler = useRef(undefined)
upHandler.current = ({ key }) => {
if (key === targetKey) setKeyPressed(false)
}

useEffect(() => {
window.addEventListener('keydown', downHandler.current)
window.addEventListener('keyup', upHandler.current)
return () => {
window.removeEventListener('keydown', downHandler.current)
window.removeEventListener('keyup', upHandler.current)
}
}, [])

return keyPressed
}

export const useOutsideClick = (ref, ref2, callback) => {
const handleClick = e => {
if (ref.current && ref.current && !ref2.current) {
Expand Down

0 comments on commit 4c0141d

Please sign in to comment.