|
|
@ -3,27 +3,46 @@ |
|
|
|
import React, { PureComponent } from 'react' |
|
|
|
import Downshift from 'downshift' |
|
|
|
import styled from 'styled-components' |
|
|
|
import { space } from 'styled-system' |
|
|
|
|
|
|
|
import type { Element } from 'react' |
|
|
|
|
|
|
|
import Box from 'components/base/Box' |
|
|
|
import Text from 'components/base/Text' |
|
|
|
import Input from 'components/base/Input' |
|
|
|
import Search from 'components/base/Search' |
|
|
|
|
|
|
|
import Triangles from './Triangles' |
|
|
|
|
|
|
|
type Props = { |
|
|
|
items: Array<Object>, |
|
|
|
itemToString: Function, |
|
|
|
onChange: Function, |
|
|
|
fuseOptions?: Object, |
|
|
|
highlight?: boolean, |
|
|
|
searchable?: boolean, |
|
|
|
placeholder?: string, |
|
|
|
renderHighlight?: string => Element<*>, |
|
|
|
renderSelected?: string => Element<*>, |
|
|
|
renderItem?: (*) => Element<*>, |
|
|
|
} |
|
|
|
|
|
|
|
const Container = styled(Box).attrs({ relative: true, color: 'steel' })`` |
|
|
|
|
|
|
|
const SearchInput = styled(Input)` |
|
|
|
border-bottom-left-radius: ${p => (p.isOpen ? 0 : '')}; |
|
|
|
border-bottom-right-radius: ${p => (p.isOpen ? 0 : '')}; |
|
|
|
const TriggerBtn = styled(Box).attrs({ |
|
|
|
p: 2, |
|
|
|
})` |
|
|
|
${space}; |
|
|
|
border: 1px solid ${p => p.theme.colors.mouse}; |
|
|
|
border-radius: 3px; |
|
|
|
display: flex; |
|
|
|
width: 100%; |
|
|
|
color: ${p => p.theme.colors.steel}; |
|
|
|
background: ${p => p.theme.colors.white}; |
|
|
|
&:focus { |
|
|
|
outline: none; |
|
|
|
box-shadow: rgba(0, 0, 0, 0.05) 0 2px 2px; |
|
|
|
} |
|
|
|
` |
|
|
|
|
|
|
|
const Item = styled(Box).attrs({ |
|
|
@ -38,65 +57,118 @@ const ItemWrapper = styled(Box)` |
|
|
|
} |
|
|
|
` |
|
|
|
|
|
|
|
const Dropdown = styled(Box)` |
|
|
|
const Dropdown = styled(Box).attrs({ |
|
|
|
mt: 1, |
|
|
|
})` |
|
|
|
position: absolute; |
|
|
|
top: 100%; |
|
|
|
left: 0; |
|
|
|
right: 0; |
|
|
|
border: 1px solid ${p => p.theme.colors.mouse}; |
|
|
|
border-top: none; |
|
|
|
max-height: 300px; |
|
|
|
overflow-y: auto; |
|
|
|
border-bottom-left-radius: 3px; |
|
|
|
border-bottom-right-radius: 3px; |
|
|
|
border-radius: 3px; |
|
|
|
box-shadow: rgba(0, 0, 0, 0.05) 0 2px 2px; |
|
|
|
` |
|
|
|
|
|
|
|
const FloatingTriangles = styled(Box).attrs({ |
|
|
|
align: 'center', |
|
|
|
justify: 'center', |
|
|
|
mr: 2, |
|
|
|
})` |
|
|
|
position: absolute; |
|
|
|
top: 0; |
|
|
|
right: 0; |
|
|
|
bottom: 0; |
|
|
|
|
|
|
|
// to "simulate" border to make arrows appears at the exact same place as
|
|
|
|
// the no-input version
|
|
|
|
padding-right: 1px; |
|
|
|
` |
|
|
|
|
|
|
|
class Select extends PureComponent<Props> { |
|
|
|
renderItems = (items: Array<Object>, downshiftProps: Object) => { |
|
|
|
const { renderItem } = this.props |
|
|
|
const { getItemProps, highlightedIndex } = downshiftProps |
|
|
|
return ( |
|
|
|
<Dropdown> |
|
|
|
{items.length ? ( |
|
|
|
items.map((item, i) => ( |
|
|
|
<ItemWrapper key={item.key} {...getItemProps({ item })}> |
|
|
|
<Item highlighted={i === highlightedIndex}> |
|
|
|
{renderItem ? renderItem(item) : <span>{item.name_highlight || item.name}</span>} |
|
|
|
</Item> |
|
|
|
</ItemWrapper> |
|
|
|
)) |
|
|
|
) : ( |
|
|
|
<ItemWrapper> |
|
|
|
<Item>{'No results'}</Item> |
|
|
|
</ItemWrapper> |
|
|
|
)} |
|
|
|
</Dropdown> |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
render() { |
|
|
|
const { items, itemToString, fuseOptions, highlight, renderHighlight, onChange } = this.props |
|
|
|
const { |
|
|
|
items, |
|
|
|
searchable, |
|
|
|
itemToString, |
|
|
|
fuseOptions, |
|
|
|
highlight, |
|
|
|
renderHighlight, |
|
|
|
renderSelected, |
|
|
|
placeholder, |
|
|
|
onChange, |
|
|
|
} = this.props |
|
|
|
|
|
|
|
return ( |
|
|
|
<Downshift |
|
|
|
itemToString={itemToString} |
|
|
|
onChange={onChange} |
|
|
|
render={({ |
|
|
|
getInputProps, |
|
|
|
getItemProps, |
|
|
|
getButtonProps, |
|
|
|
getRootProps, |
|
|
|
isOpen, |
|
|
|
inputValue, |
|
|
|
highlightedIndex, |
|
|
|
openMenu, |
|
|
|
selectedItem, |
|
|
|
...downshiftProps |
|
|
|
}) => ( |
|
|
|
<Container {...getRootProps({ refKey: 'innerRef' })}> |
|
|
|
<SearchInput |
|
|
|
keepEvent |
|
|
|
{...getInputProps({ placeholder: 'Chess?' })} |
|
|
|
isOpen={isOpen} |
|
|
|
onClick={openMenu} |
|
|
|
/> |
|
|
|
{isOpen && ( |
|
|
|
<Search |
|
|
|
value={inputValue} |
|
|
|
items={items} |
|
|
|
fuseOptions={fuseOptions} |
|
|
|
highlight={highlight} |
|
|
|
renderHighlight={renderHighlight} |
|
|
|
render={items => |
|
|
|
items.length ? ( |
|
|
|
<Dropdown> |
|
|
|
{items.map((item, i) => ( |
|
|
|
<ItemWrapper key={item.key} {...getItemProps({ item })}> |
|
|
|
<Item highlighted={i === highlightedIndex}> |
|
|
|
<span>{item.name_highlight || item.name}</span> |
|
|
|
</Item> |
|
|
|
</ItemWrapper> |
|
|
|
))} |
|
|
|
</Dropdown> |
|
|
|
) : null |
|
|
|
} |
|
|
|
/> |
|
|
|
{searchable ? ( |
|
|
|
<Box relative> |
|
|
|
<Input keepEvent {...getInputProps({ placeholder })} onClick={openMenu} /> |
|
|
|
<FloatingTriangles> |
|
|
|
<Triangles /> |
|
|
|
</FloatingTriangles> |
|
|
|
</Box> |
|
|
|
) : ( |
|
|
|
<TriggerBtn {...getButtonProps()} tabIndex={0} horizontal align="center" flow={2}> |
|
|
|
<Box grow> |
|
|
|
{selectedItem ? ( |
|
|
|
renderSelected(selectedItem) |
|
|
|
) : ( |
|
|
|
<Text color="mouse">{placeholder}</Text> |
|
|
|
)} |
|
|
|
</Box> |
|
|
|
<Triangles /> |
|
|
|
</TriggerBtn> |
|
|
|
)} |
|
|
|
{isOpen && |
|
|
|
(searchable ? ( |
|
|
|
<Search |
|
|
|
value={inputValue} |
|
|
|
items={items} |
|
|
|
fuseOptions={fuseOptions} |
|
|
|
highlight={highlight} |
|
|
|
renderHighlight={renderHighlight} |
|
|
|
render={items => this.renderItems(items, downshiftProps)} |
|
|
|
/> |
|
|
|
) : ( |
|
|
|
this.renderItems(items, downshiftProps) |
|
|
|
))} |
|
|
|
</Container> |
|
|
|
)} |
|
|
|
/> |
|
|
|