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 Sep 5, 2020
1 parent a825109 commit 53cc856
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 24 deletions.
105 changes: 81 additions & 24 deletions src/components/Search/index.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
import React, { useState, useEffect, useMemo, useRef } from 'react'
import styled from 'styled-components'

import Row, { RowFixed } from '../Row'
import TokenLogo from '../TokenLogo'
import { transparentize } from 'polished'
import React, { useEffect, useMemo, useRef, useState } from 'react'
import { Search as SearchIcon, X } from 'react-feather'
import { BasicLink } from '../Link'

import { useAllTokenData, useTokenData } from '../../contexts/TokenData'
import { useAllPairData, usePairData } from '../../contexts/PairData'
import DoubleTokenLogo from '../DoubleLogo'
import { useMedia } from 'react-use'
import { useAllPairsInUniswap, useAllTokensInUniswap } from '../../contexts/GlobalData'
import { OVERVIEW_TOKEN_BLACKLIST, PAIR_BLACKLIST } from '../../constants'

import { transparentize } from 'polished'
import styled from 'styled-components'
import { client } from '../../apollo/client'
import { PAIR_SEARCH, TOKEN_SEARCH } from '../../apollo/queries'
import FormattedName from '../FormattedName'
import { OVERVIEW_TOKEN_BLACKLIST, PAIR_BLACKLIST } from '../../constants'
import { useAllPairsInUniswap, useAllTokensInUniswap } from '../../contexts/GlobalData'
import { useAllPairData, usePairData } from '../../contexts/PairData'
import { useAllTokenData, useTokenData } from '../../contexts/TokenData'
import { useKeyPress } from '../../hooks'
import { TYPE } from '../../Theme'
import DoubleTokenLogo from '../DoubleLogo'
import FormattedName from '../FormattedName'
import { BasicLink } from '../Link'
import Row, { RowFixed } from '../Row'
import TokenLogo from '../TokenLogo'

const Container = styled.div`
height: 48px;
Expand Down Expand Up @@ -110,7 +108,7 @@ const Menu = styled.div`
width: 100%;
top: 50px;
max-height: 540px;
overflow: scroll;
overflow-y: scroll;
left: 0;
padding-bottom: 20px;
background: ${({ theme }) => theme.bg6};
Expand All @@ -131,6 +129,10 @@ const MenuItem = styled(Row)`
cursor: pointer;
background-color: ${({ theme }) => theme.bg2};
}
:focus {
background-color: #f7f8fa;
outline: black auto 1px;
}
`

const Heading = styled(Row)`
Expand All @@ -156,7 +158,13 @@ export const Search = ({ small = false }) => {
let 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 @@ -171,9 +179,9 @@ export const Search = ({ small = false }) => {

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

Expand Down Expand Up @@ -388,10 +396,59 @@ export const Search = ({ small = false }) => {
const [tokensShown, setTokensShown] = useState(3)
const [pairsShown, setPairsShown] = useState(3)

useEffect(() => {
if (pairsShown + tokensShown === 0) {
// no results, so do nothing
setCursor(undefined)
} else if (showMenu && downKeyPressed && cursor === undefined) {
// no active cursor, start from the top of the list
setCursor(0)
} else if (showMenu && downKeyPressed && cursor < pairsShown + tokensShown - 1) {
// existing cursor, increment down the length of the list
setCursor(cursor + 1)
}
// @ts-ignore
}, [showMenu, downKeyPressed, pairsShown, tokensShown])

useEffect(() => {
if (pairsShown + tokensShown === 0) {
// no results, so do nothing
setCursor(undefined)
} else if (showMenu && upKeyPressed && cursor === undefined) {
// start from the bottom of the list
setCursor(pairsShown + tokensShown - 1)
} else if (showMenu && upKeyPressed && cursor === 0) {
// the user clicked up from index 0, so loop them to the bottom of the list
setCursor(pairsShown + tokensShown - 1)
} else if (showMenu && upKeyPressed && cursor > 0) {
// continue down the list
setCursor(cursor - 1)
}
// @ts-ignore
}, [pairsShown, showMenu, tokensShown, 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 @@ -406,7 +463,7 @@ export const Search = ({ small = false }) => {
) {
setPairsShown(3)
setTokensShown(3)
toggleMenu(false)
setShowMenu(false)
}
}

Expand All @@ -433,19 +490,19 @@ export const Search = ({ small = false }) => {
? 'Search Uniswap...'
: below700
? 'Search pairs and tokens...'
: 'Search Uniswap pairs and tokens...'
: `Search Uniswap pairs and tokens... ${cursor}`
}
value={value}
onChange={e => {
setValue(e.target.value)
}}
onFocus={() => {
if (!showMenu) {
toggleMenu(true)
setShowMenu(true)
}
}}
/>
{!showMenu ? <SearchIconLarge /> : <CloseIcon onClick={() => toggleMenu(false)} />}
{!showMenu ? <SearchIconLarge /> : <CloseIcon onClick={() => setShowMenu(false)} />}
</Wrapper>
<Menu hide={!showMenu} ref={menuRef}>
<Heading>
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 53cc856

Please sign in to comment.