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. 112
      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) {
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<Props, State> {
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) {

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

@ -3,6 +3,7 @@
import React, { PureComponent } from 'react'
import Downshift from 'downshift'
import styled from 'styled-components'
import { space } from 'styled-system'
import type { Element } from 'react'
@ -16,14 +17,29 @@ type Props = {
onChange: Function,
fuseOptions?: Object,
highlight?: boolean,
searchable?: boolean,
renderHighlight?: string => Element<*>,
renderItem?: (*) => Element<*>,
}
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-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({
@ -38,65 +54,93 @@ 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;
`
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,
onChange,
} = this.props
return (
<Downshift
itemToString={itemToString}
onChange={onChange}
render={({
getInputProps,
getItemProps,
getButtonProps,
getRootProps,
isOpen,
inputValue,
highlightedIndex,
openMenu,
...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 ? (
<Input
keepEvent
{...getInputProps({ placeholder: 'Chess?' })}
isOpen={isOpen}
onClick={openMenu}
/>
) : (
<TriggerBtn isOpen={isOpen} {...getButtonProps()} tabIndex={0}>
lablala
</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>
)}
/>

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 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,52 @@ 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 (
<div>
{children(this.handleChange)}
{item && (
<Box mt={2}>
<pre>
{'You selected:'}
{JSON.stringify(item)}
</pre>
</Box>
)}
</div>
)
}
}
stories.add('basic', () => (
<Wrapper>
{onChange => (
<Select
items={itemsChessPlayers}
itemToString={item => (item ? item.name : '')}
onChange={onChange}
/>
)}
</Wrapper>
))
stories.add('searchable', () => (
<Select
items={items}
items={itemsChessPlayers}
searchable
highlight
fuseOptions={{ keys: ['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