Browse Source

Make the Select explicitely searchable

master
meriadec 7 years ago
parent
commit
3dfe9a7754
No known key found for this signature in database GPG Key ID: 1D2FC2305E2CB399
  1. 6
      src/components/base/Search/index.js
  2. 92
      src/components/base/Select/index.js
  3. 82
      src/components/base/Select/stories.js

6
src/components/base/Search/index.js

@ -42,7 +42,8 @@ class Search extends PureComponent<Props, State> {
componentWillReceiveProps(nextProps: Props) { componentWillReceiveProps(nextProps: Props) {
if (nextProps.value !== this.props.value) { if (nextProps.value !== this.props.value) {
if (this._fuse) { 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) { if (nextProps.highlight !== this.props.highlight) {
@ -63,7 +64,8 @@ class Search extends PureComponent<Props, State> {
includeMatches: highlight, includeMatches: highlight,
}) })
this.formatResults(this._fuse.search(value), props) const results = this._fuse.search(value)
this.formatResults(results, props)
} }
formatResults(results: Array<Object>, props: Props) { formatResults(results: Array<Object>, props: Props) {

92
src/components/base/Select/index.js

@ -3,6 +3,7 @@
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
import Downshift from 'downshift' import Downshift from 'downshift'
import styled from 'styled-components' import styled from 'styled-components'
import { space } from 'styled-system'
import type { Element } from 'react' import type { Element } from 'react'
@ -16,14 +17,29 @@ type Props = {
onChange: Function, onChange: Function,
fuseOptions?: Object, fuseOptions?: Object,
highlight?: boolean, highlight?: boolean,
searchable?: boolean,
renderHighlight?: string => Element<*>, renderHighlight?: string => Element<*>,
renderItem?: (*) => Element<*>,
} }
const Container = styled(Box).attrs({ relative: true, color: 'steel' })`` const Container = styled(Box).attrs({ relative: true, color: 'steel' })``
const SearchInput = styled(Input)` 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};
border-bottom-left-radius: ${p => (p.isOpen ? 0 : '')}; border-bottom-left-radius: ${p => (p.isOpen ? 0 : '')};
border-bottom-right-radius: ${p => (p.isOpen ? 0 : '')}; border-bottom-right-radius: ${p => (p.isOpen ? 0 : '')};
&:focus {
outline: none;
box-shadow: rgba(0, 0, 0, 0.05) 0 2px 2px;
}
` `
const Item = styled(Box).attrs({ const Item = styled(Box).attrs({
@ -38,65 +54,93 @@ const ItemWrapper = styled(Box)`
} }
` `
const Dropdown = styled(Box)` const Dropdown = styled(Box).attrs({
mt: 1,
})`
position: absolute; position: absolute;
top: 100%; top: 100%;
left: 0; left: 0;
right: 0; right: 0;
border: 1px solid ${p => p.theme.colors.mouse}; border: 1px solid ${p => p.theme.colors.mouse};
border-top: none;
max-height: 300px; max-height: 300px;
overflow-y: auto; overflow-y: auto;
border-bottom-left-radius: 3px; border-radius: 3px;
border-bottom-right-radius: 3px;
box-shadow: rgba(0, 0, 0, 0.05) 0 2px 2px; box-shadow: rgba(0, 0, 0, 0.05) 0 2px 2px;
` `
class Select extends PureComponent<Props> { 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() { render() {
const { items, itemToString, fuseOptions, highlight, renderHighlight, onChange } = this.props const {
items,
searchable,
itemToString,
fuseOptions,
highlight,
renderHighlight,
onChange,
} = this.props
return ( return (
<Downshift <Downshift
itemToString={itemToString} itemToString={itemToString}
onChange={onChange} onChange={onChange}
render={({ render={({
getInputProps, getInputProps,
getItemProps, getButtonProps,
getRootProps, getRootProps,
isOpen, isOpen,
inputValue, inputValue,
highlightedIndex,
openMenu, openMenu,
...downshiftProps
}) => ( }) => (
<Container {...getRootProps({ refKey: 'innerRef' })}> <Container {...getRootProps({ refKey: 'innerRef' })}>
<SearchInput {searchable ? (
<Input
keepEvent keepEvent
{...getInputProps({ placeholder: 'Chess?' })} {...getInputProps({ placeholder: 'Chess?' })}
isOpen={isOpen} isOpen={isOpen}
onClick={openMenu} onClick={openMenu}
/> />
{isOpen && ( ) : (
<TriggerBtn isOpen={isOpen} {...getButtonProps()} tabIndex={0}>
lablala
</TriggerBtn>
)}
{isOpen &&
(searchable ? (
<Search <Search
value={inputValue} value={inputValue}
items={items} items={items}
fuseOptions={fuseOptions} fuseOptions={fuseOptions}
highlight={highlight} highlight={highlight}
renderHighlight={renderHighlight} renderHighlight={renderHighlight}
render={items => render={items => this.renderItems(items, downshiftProps)}
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
}
/> />
)} ) : (
this.renderItems(items, downshiftProps)
))}
</Container> </Container>
)} )}
/> />

82
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 { storiesOf } from '@storybook/react'
import Box from 'components/base/Box'
import Select from 'components/base/Select' import Select from 'components/base/Select'
import Text from 'components/base/Text' import Text from 'components/base/Text'
const stories = storiesOf('Select', module) const stories = storiesOf('Select', module)
const items = [ const itemsChessPlayers = [
{ key: 'aleksandr-grichtchouk', name: 'Aleksandr Grichtchouk' }, { key: 'aleksandr-grichtchouk', name: 'Aleksandr Grichtchouk' },
{ key: 'fabiano-caruana', name: 'Fabiano Caruana' }, { key: 'fabiano-caruana', name: 'Fabiano Caruana' },
{ key: 'garry-kasparov', name: 'Garry Kasparov' }, { key: 'garry-kasparov', name: 'Garry Kasparov' },
@ -20,9 +22,52 @@ const items = [
{ key: 'vladimir-kramnik', name: 'Vladimir Kramnik' }, { 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 (
<div>
{children(this.handleChange)}
{item && (
<Box mt={2}>
<pre>
{'You selected:'}
{JSON.stringify(item)}
</pre>
</Box>
)}
</div>
)
}
}
stories.add('basic', () => ( stories.add('basic', () => (
<Wrapper>
{onChange => (
<Select <Select
items={items} items={itemsChessPlayers}
itemToString={item => (item ? item.name : '')}
onChange={onChange}
/>
)}
</Wrapper>
))
stories.add('searchable', () => (
<Select
items={itemsChessPlayers}
searchable
highlight highlight
fuseOptions={{ keys: ['name'] }} fuseOptions={{ keys: ['name'] }}
itemToString={item => (item ? item.name : '')} itemToString={item => (item ? item.name : '')}
@ -33,3 +78,34 @@ 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', () => (
<Select
items={itemsColors}
highlight
searchable
fuseOptions={{ keys: ['name', 'color'] }}
itemToString={item => (item ? item.name : '')}
renderHighlight={(text, key) => (
<Text key={key} fontWeight="bold">
{text}
</Text>
)}
renderItem={item => (
<Box horizontal flow={2}>
<Box bg={item.color} style={{ width: 20, height: 20 }} />
<span>{item.name_highlight || item.name}</span>
</Box>
)}
/>
))

Loading…
Cancel
Save