Skip to content

Commit

Permalink
Tokens: Local identities autocomplete (#862)
Browse files Browse the repository at this point in the history
* Hooks: add helper utils

* AutoComplete: add component

* LocalIdentitiesAutoComplete: add component

* AssignVotePanelContent: use local identities autocomplete

* AutoComplete: trigger onChange when closing

* LocalIdentitiesAutoComplete: handle receiving two types of data in change handler

* Autocomplete: add styles and props for setting inner styles

* LocalIdentitiesAutoComplete: update styles for autocomplete buttons

* IconMagnifyingGlass: add component

* AutoComplete: add magnifying glass icon

* AutoComplete: add animated transition when opening list

* LocalIdentitiesAutoComplete: fix sending data to onChange handler

* IconMagnifyingGlass: add react memo

* AutoComplete: add react memo and useCallback

* AutoComplete: add prop types

* LocalIdentitiesAutoComplete: update to a more simulated api

* LocalIdentitiesAutoComplete: update item and selected to functions

* LocalIdentitiesAutoComplete: use callback on item and selected functions

* LocalIdentityBadge: update effect on default value to simulate final api

* LocalIdentityBadge: remove unused prop

* LocalIdentityBadge: fix error on default value

* LocalIdentityBadge: add prop types

* LocalIdentityBadge: add react memo

* LocalIdentityBadge: add useCallback to handlers

* Autocomplete: remove selected functionality from component

* LocalIdentityBadge: add selected functionality to component

* Autocomplete: remove unused vars

* LocalIdentityBadge: add useCallback to handlers and remove unused vars

* LocalIdentityBadge: add onChange with selected name when clicking on selected option

* Integrate local identities autocomplete with updated api (#872)

* Use api to search identities

* LocalIdentitiesAutoComplete: remove mock data and integrate with aragon api

* LocalIdentitiesAutoComplete: fix deps on hooks

* AutoComplete: add default prop and fix lint error

* AutoCompleteSelected: add component that abstracts selected functionality

* LocalIdentitiesAutoComplete: refactor to use AutoCompleteSelected

* AssignVotePanelContent: set height for container

* Correctly setup react.memo and react.forwardref

* AutoComplete: remove unused dep

* AutoCompleteSelected: add reference to props

* Hooks: add hook to cycle through items using arrow keys

* AutoComplete: add hook to cycle through items using arrow keys

* Update aragon api and api react versions

* useArrowKeysFocus: update var names

* AutoComplete: update react memo and forwardref usage

* AutoCompleteSelected: update react memo and forwardref usage

* Finance: local identities autocomplete (#877)

* Hooks: add hook helpers

* AutoComplete: add component

* AutoCompleteSelected: add component

* IconMagnifyingGlass: add component

* LocalIdentitiesAutoComplete: add component

* Withdrawal: update recipient text input for autocomplete input

* AutoComplete: remove unused dep

* AutoCompleteSelected: add reference to props

* Hooks: add hook to cycle through items using arrow keys

* AutoComplete: add hook to cycle through items using arrow keys

* useArrowKeysFocus: update var names

* AutoComplete: update hook var names

* Update aragon api and api react versions

* useArrowKeysFocus: update var names

* AutoComplete: update react memo and forwardref usage

* AutoCompleteSelected: update react memo and forwardref usage
  • Loading branch information
AquiGorka authored Jun 13, 2019
1 parent 12c30c6 commit 8c81ed9
Show file tree
Hide file tree
Showing 14 changed files with 1,150 additions and 12 deletions.
4 changes: 2 additions & 2 deletions apps/finance/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"private": true,
"license": "AGPL-3.0-or-later",
"dependencies": {
"@aragon/api": "^2.0.0-beta.3",
"@aragon/api-react": "^2.0.0-beta.1",
"@aragon/api": "^2.0.0-beta.4",
"@aragon/api-react": "^2.0.0-beta.4",
"@aragon/templates-tokens": "^1.2.0",
"@aragon/ui": "^0.40.0",
"@babel/polyfill": "^7.0.0",
Expand Down
163 changes: 163 additions & 0 deletions apps/finance/app/src/components/AutoComplete/AutoComplete.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import React, { useState, useRef, useCallback } from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import { Transition, animated } from 'react-spring'
import { ButtonBase, TextInput, springs, theme, unselectable } from '@aragon/ui'
import { useClickOutside, useOnBlur, useArrowKeysFocus } from '../../hooks'
import IconMagnifyingGlass from './IconMagnifyingGlass'

const { accent, contentBackground, contentBorder, textPrimary } = theme
const identity = x => x

function AutoComplete({
forwardedRef,
itemButtonStyles = '',
items,
onSelect,
onChange,
renderItem = identity,
required,
value,
wide,
}) {
const ref = forwardedRef
const [opened, setOpened] = useState(true)
const wrapRef = useRef()

const handleClose = useCallback(() => setOpened(false))
const handleFocus = useCallback(() => setOpened(true))
const handleSelect = useCallback(
item => e => {
e.preventDefault()
onSelect(item)
},
[onSelect]
)
const handleChange = useCallback(({ target: { value } }) => onChange(value), [
onChange,
])

const { containerRef, handleContainerBlur } = useArrowKeysFocus(
'.autocomplete-items'
)
const { handleBlur } = useOnBlur(handleClose, wrapRef)
useClickOutside(handleClose, wrapRef)

return (
<div css="position: relative" ref={wrapRef} onBlur={handleBlur}>
<TextInput
css={`
caret-color: ${accent};
padding-right: 35px;
`}
ref={ref}
wide={wide}
required={required}
onChange={handleChange}
onFocus={handleFocus}
value={value}
/>
<div
css={`
position: absolute;
top: 0;
right: 0;
height: 40px;
width: 35px;
display: flex;
align-items: center;
justify-content: center;
`}
>
<IconMagnifyingGlass css="color: #a8b3c8" />
</div>
<Transition
config={springs.swift}
items={opened && !!items.length}
from={{ scale: 0.98, opacity: 0 }}
enter={{ scale: 1, opacity: 1 }}
leave={{ scale: 1, opacity: 0 }}
native
>
{show =>
show &&
(({ scale, opacity }) => (
<Items
ref={containerRef}
onBlur={handleContainerBlur}
role="listbox"
style={{
opacity,
transform: scale.interpolate(t => `scale3d(${t},${t},1)`),
}}
>
{items.map(item => (
<Item role="option" key={item.key}>
<ButtonBase
className="autocomplete-items"
onClick={handleSelect(item)}
css={`
width: 100%;
${itemButtonStyles};
`}
>
{renderItem(item, value)}
</ButtonBase>
</Item>
))}
</Items>
))
}
</Transition>
</div>
)
}

AutoComplete.propTypes = {
forwardedRef: PropTypes.object,
itemButtonStyles: PropTypes.string,
items: PropTypes.array.isRequired,
onSelect: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
renderItem: PropTypes.func,
required: PropTypes.bool,
value: PropTypes.string,
wide: PropTypes.bool,
}

const Item = styled.li`
${unselectable()};
overflow: hidden;
cursor: pointer;
`

const Items = styled(animated.ul)`
position: absolute;
z-index: 2;
top: 100%;
width: 100%;
padding: 8px 0;
color: ${textPrimary};
background: ${contentBackground};
border: 1px solid ${contentBorder};
box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.06);
border-radius: 3px;
padding: 0;
margin: 0;
list-style: none;
& ${Item}:first-child {
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
& ${Item}:last-child {
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
}
`

const AutoCompleteMemo = React.memo(AutoComplete)

export default React.forwardRef((props, ref) => (
<AutoCompleteMemo {...props} forwardedRef={ref} />
))
113 changes: 113 additions & 0 deletions apps/finance/app/src/components/AutoComplete/AutoCompleteSelected.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import React, { useRef, useCallback } from 'react'
import PropTypes from 'prop-types'
import { ButtonBase, theme } from '@aragon/ui'
import AutoComplete from './AutoComplete'

const identity = x => x
const noop = () => null

function AutoCompleteSelected({
forwardedRef,
itemButtonStyles,
items,
onChange,
onSelect, // user clicks on an item in the list and thus, selects it
onSelectedClick = noop, // when item is selected and user clicks on it, opens up the input for typing
renderItem,
required,
renderSelected = identity,
selected,
selectedButtonStyles = '',
value,
wide,
}) {
const ref = forwardedRef
const selectedRef = useRef()

const handleSelect = useCallback(
selected => {
onSelect(selected)
setTimeout(() => {
selectedRef.current.focus()
}, 0)
},
[onChange]
)
const handleSelectedClick = useCallback(
() => {
onSelectedClick()
setTimeout(() => {
ref.current.select()
ref.current.focus()
}, 0)
},
[ref, selected, onChange]
)

if (selected) {
return (
<ButtonBase
onClick={handleSelectedClick}
ref={selectedRef}
css={`
height: 40px;
width: 100%;
background: #fff;
cursor: pointer;
border: 1px solid ${theme.contentBorder};
border-radius: 3px;
${selectedButtonStyles};
`}
>
{renderSelected(selected)}
</ButtonBase>
)
}

return (
<AutoComplete
itemButtonStyles={`
border-left: 3px solid transparent;
cursor: pointer;
border-radius: 0;
&:hover,
&:focus {
outline: 2px solid ${theme.accent};
background: #f9fafc;
border-left: 3px solid ${theme.accent}
}
`}
items={items}
onChange={onChange}
onSelect={handleSelect}
ref={ref}
renderItem={renderItem}
required={required}
value={value}
wide={wide}
/>
)
}

AutoCompleteSelected.propTypes = {
forwardedRef: PropTypes.object,
itemButtonStyles: PropTypes.string,
items: PropTypes.array.isRequired,
onChange: PropTypes.func.isRequired,
onSelect: PropTypes.func.isRequired,
onSelectedClick: PropTypes.func,
renderItem: PropTypes.func,
renderSelected: PropTypes.func,
required: PropTypes.bool,
selected: PropTypes.object,
selectedButtonStyles: PropTypes.string,
value: PropTypes.string,
wide: PropTypes.bool,
}

const AutoCompleteSelectedMemo = React.memo(AutoCompleteSelected)

export default React.forwardRef((props, ref) => (
<AutoCompleteSelectedMemo {...props} forwardedRef={ref} />
))
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react'

const IconMagnifyingGlass = React.memo(props => {
return (
<svg width={16} height={16} fill="none" {...props}>
<path
d="M15.757 14.573l-3.944-3.96a6.307 6.307 0 0 0 1.57-4.153C13.382 2.898 10.38 0 6.69 0 3.001 0 0 2.898 0 6.46s3.002 6.46 6.691 6.46a6.784 6.784 0 0 0 3.834-1.169l3.974 3.99c.166.167.39.259.629.259a.885.885 0 0 0 .605-.235.823.823 0 0 0 .024-1.192zM6.69 1.685c2.727 0 4.946 2.142 4.946 4.775 0 2.633-2.219 4.775-4.946 4.775S1.746 9.093 1.746 6.46c0-2.633 2.218-4.775 4.945-4.775z"
fill="currentColor"
/>
</svg>
)
})

export default IconMagnifyingGlass
Loading

0 comments on commit 8c81ed9

Please sign in to comment.