Browse Source

Merge pull request #31 from meriadec/master

Select component. Enhanced.
master
Loëck Vézien 7 years ago
committed by GitHub
parent
commit
2363128f02
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      src/components/base/Search/index.js
  2. 29
      src/components/base/Select/Triangles.js
  3. 146
      src/components/base/Select/index.js
  4. 85
      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) {

29
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 }) => (
<Box flow={1}>
<UpTriangle size={size} color={color} />
<DownTriangle size={size} color={color} />
</Box>
)

146
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<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>
)}
/>

85
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 (
<div>
{children(this.handleChange)}
{item && (
<Box mt={2}>
<pre>
{'You selected:'}
{JSON.stringify(item)}
</pre>
</Box>
)}
</div>
)
}
}
stories.add('basic', () => (
<Wrapper>
{onChange => (
<Select
placeholder="Choose a chess player..."
items={itemsChessPlayers}
renderSelected={item => item.name}
onChange={onChange}
/>
)}
</Wrapper>
))
stories.add('searchable', () => (
<Select
items={items}
placeholder="Choose a chess player..."
items={itemsChessPlayers}
searchable
highlight
fuseOptions={{ keys: ['name'] }}
itemToString={item => (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', () => (
<Select
placeholder="Choose a color..."
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