diff --git a/src/components/base/Search/index.js b/src/components/base/Search/index.js index f1945bb5..d8927ccf 100644 --- a/src/components/base/Search/index.js +++ b/src/components/base/Search/index.js @@ -42,7 +42,8 @@ class Search extends PureComponent { componentWillReceiveProps(nextProps: Props) { if (nextProps.value !== this.props.value) { if (this._fuse) { - this.formatResults(this._fuse.search(nextProps.value), nextProps) + const results = this._fuse.search(nextProps.value) + this.formatResults(results, nextProps) } } if (nextProps.highlight !== this.props.highlight) { @@ -63,7 +64,8 @@ class Search extends PureComponent { includeMatches: highlight, }) - this.formatResults(this._fuse.search(value), props) + const results = this._fuse.search(value) + this.formatResults(results, props) } formatResults(results: Array, props: Props) { diff --git a/src/components/base/Select/Triangles.js b/src/components/base/Select/Triangles.js new file mode 100644 index 00000000..a41f81bc --- /dev/null +++ b/src/components/base/Select/Triangles.js @@ -0,0 +1,29 @@ +// @flow + +import React from 'react' +import styled from 'styled-components' + +import Box from 'components/base/Box' + +const UpTriangle = styled.div` + width: 0; + height: 0; + border-left: ${p => p.size}px solid transparent; + border-right: ${p => p.size}px solid transparent; + border-bottom: ${p => p.size}px solid ${p => p.theme.colors[p.color]}; +` + +const DownTriangle = styled.div` + width: 0; + height: 0; + border-left: ${p => p.size}px solid transparent; + border-right: ${p => p.size}px solid transparent; + border-top: ${p => p.size}px solid ${p => p.theme.colors[p.color]}; +` + +export default ({ size = 5, color = 'mouse' }: { size: number, color: string }) => ( + + + + +) diff --git a/src/components/base/Select/index.js b/src/components/base/Select/index.js index ae98ff35..5591e59b 100644 --- a/src/components/base/Select/index.js +++ b/src/components/base/Select/index.js @@ -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, 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 { + renderItems = (items: Array, downshiftProps: Object) => { + const { renderItem } = this.props + const { getItemProps, highlightedIndex } = downshiftProps + return ( + + {items.length ? ( + items.map((item, i) => ( + + + {renderItem ? renderItem(item) : {item.name_highlight || item.name}} + + + )) + ) : ( + + {'No results'} + + )} + + ) + } + render() { - const { items, itemToString, fuseOptions, highlight, renderHighlight, onChange } = this.props + const { + items, + searchable, + itemToString, + fuseOptions, + highlight, + renderHighlight, + renderSelected, + placeholder, + onChange, + } = this.props + return ( ( - - {isOpen && ( - - items.length ? ( - - {items.map((item, i) => ( - - - {item.name_highlight || item.name} - - - ))} - - ) : null - } - /> + {searchable ? ( + + + + + + + ) : ( + + + {selectedItem ? ( + renderSelected(selectedItem) + ) : ( + {placeholder} + )} + + + )} + {isOpen && + (searchable ? ( + this.renderItems(items, downshiftProps)} + /> + ) : ( + this.renderItems(items, downshiftProps) + ))} )} /> diff --git a/src/components/base/Select/stories.js b/src/components/base/Select/stories.js index 8c35a4fe..ab2f826a 100644 --- a/src/components/base/Select/stories.js +++ b/src/components/base/Select/stories.js @@ -1,12 +1,14 @@ -import React from 'react' +import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' import { storiesOf } from '@storybook/react' +import Box from 'components/base/Box' import Select from 'components/base/Select' import Text from 'components/base/Text' const stories = storiesOf('Select', module) -const items = [ +const itemsChessPlayers = [ { key: 'aleksandr-grichtchouk', name: 'Aleksandr Grichtchouk' }, { key: 'fabiano-caruana', name: 'Fabiano Caruana' }, { key: 'garry-kasparov', name: 'Garry Kasparov' }, @@ -20,9 +22,54 @@ const items = [ { key: 'vladimir-kramnik', name: 'Vladimir Kramnik' }, ] +class Wrapper extends PureComponent { + static propTypes = { + children: PropTypes.func.isRequired, + } + + state = { + item: null, + } + + handleChange = item => this.setState({ item }) + + render() { + const { children } = this.props + const { item } = this.state + return ( +
+ {children(this.handleChange)} + {item && ( + +
+              {'You selected:'}
+              {JSON.stringify(item)}
+            
+
+ )} +
+ ) + } +} + stories.add('basic', () => ( + + {onChange => ( + (item ? item.name : '')} @@ -33,3 +80,35 @@ stories.add('basic', () => ( )} /> )) + +const itemsColors = [ + { key: 'absolute zero', name: 'Absolute Zero', color: '#0048BA' }, + { key: 'acid green', name: 'Acid Green', color: '#B0BF1A' }, + { key: 'aero', name: 'Aero', color: '#7CB9E8' }, + { key: 'aero blue', name: 'Aero Blue', color: '#C9FFE5' }, + { key: 'african violet', name: 'African Violet', color: '#B284BE' }, + { key: 'air force blue (usaf)', name: 'Air Force Blue (USAF)', color: '#00308F' }, + { key: 'air superiority blue', name: 'Air Superiority Blue', color: '#72A0C1' }, +] + +stories.add('custom render', () => ( +