Meriadec Pillet
7 years ago
committed by
GitHub
6 changed files with 26 additions and 490 deletions
@ -1,347 +0,0 @@ |
|||
// @flow
|
|||
|
|||
import React, { PureComponent } from 'react' |
|||
import Downshift from 'downshift' |
|||
import styled from 'styled-components' |
|||
import { space } from 'styled-system' |
|||
import { translate } from 'react-i18next' |
|||
|
|||
import type { T } from 'types/common' |
|||
|
|||
import Box from 'components/base/Box' |
|||
import GrowScroll from 'components/base/GrowScroll' |
|||
import Input from 'components/base/Input' |
|||
import Search from 'components/base/Search' |
|||
import Text from 'components/base/Text' |
|||
|
|||
import IconCheck from 'icons/Check' |
|||
|
|||
type Props = { |
|||
bg?: string, |
|||
flatLeft?: boolean, |
|||
flatRight?: boolean, |
|||
fakeFocusRight?: boolean, |
|||
fuseOptions?: Object, |
|||
highlight?: boolean, |
|||
items: Array<any>, |
|||
itemToString?: Function, |
|||
keyProp?: string, |
|||
maxHeight?: number, |
|||
onChange?: Function, |
|||
placeholder?: string, |
|||
renderHighlight?: string => React$Node, |
|||
renderItem?: (*) => React$Node, |
|||
renderSelected?: any => React$Node, |
|||
searchable?: boolean, |
|||
value?: *, |
|||
disabled: boolean, |
|||
small?: boolean, |
|||
t: T, |
|||
} |
|||
|
|||
const Container = styled(Box).attrs({ relative: true, color: 'graphite' })`` |
|||
|
|||
const TriggerBtn = styled(Box).attrs({ |
|||
alignItems: 'center', |
|||
ff: p => (p.small ? 'Open Sans' : 'Open Sans|SemiBold'), |
|||
flow: 2, |
|||
fontSize: p => (p.small ? 3 : 4), |
|||
horizontal: true, |
|||
px: 3, |
|||
})` |
|||
${space}; |
|||
height: ${p => (p.small ? '34' : '40')}px; |
|||
background: ${p => (p.disabled ? p.theme.colors.lightGrey : p.bg || p.theme.colors.white)}; |
|||
border-bottom-left-radius: ${p => (p.flatLeft ? 0 : p.theme.radii[1])}px; |
|||
border-bottom-right-radius: ${p => (p.flatRight ? 0 : p.theme.radii[1])}px; |
|||
border-top-left-radius: ${p => (p.flatLeft ? 0 : p.theme.radii[1])}px; |
|||
border-top-right-radius: ${p => (p.flatRight ? 0 : p.theme.radii[1])}px; |
|||
border: 1px solid ${p => p.theme.colors.fog}; |
|||
color: ${p => p.theme.colors.graphite}; |
|||
cursor: ${p => (p.disabled ? 'cursor' : 'pointer')}; |
|||
display: flex; |
|||
width: 100%; |
|||
|
|||
&:focus { |
|||
outline: none; |
|||
${p => |
|||
p.disabled |
|||
? '' |
|||
: ` |
|||
border-color: ${p.theme.colors.wallet}; |
|||
box-shadow: rgba(0, 0, 0, 0.05) 0 2px 2px;`};
|
|||
} |
|||
|
|||
${p => { |
|||
const c = p.theme.colors.wallet |
|||
return p.fakeFocusRight |
|||
? ` |
|||
border-top: 1px solid ${c}; |
|||
border-right: 1px solid ${c}; |
|||
border-bottom: 1px solid ${c}; |
|||
` |
|||
: '' |
|||
}}; |
|||
` |
|||
|
|||
const Item = styled(Box).attrs({ |
|||
alignItems: 'center', |
|||
fontSize: 4, |
|||
ff: p => `Open Sans|${p.selected ? 'SemiBold' : 'Regular'}`, |
|||
px: 3, |
|||
py: 2, |
|||
color: 'dark', |
|||
})` |
|||
background: ${p => (p.highlighted ? p.theme.colors.lightGrey : p.theme.colors.white)}; |
|||
|
|||
${p => |
|||
p.first && |
|||
` |
|||
border-top-left-radius: ${p.theme.radii[1]}px; |
|||
border-top-right-radius: ${p.theme.radii[1]}px; |
|||
`} ${p => |
|||
p.last && |
|||
` |
|||
border-bottom-left-radius: ${p.theme.radii[1]}px; |
|||
border-bottom-right-radius: ${p.theme.radii[1]}px; |
|||
`};
|
|||
` |
|||
|
|||
const Dropdown = styled(Box).attrs({ |
|||
mt: 1, |
|||
})` |
|||
border-radius: ${p => p.theme.radii[1]}px; |
|||
border: 1px solid ${p => p.theme.colors.fog}; |
|||
box-shadow: rgba(0, 0, 0, 0.05) 0 2px 2px; |
|||
left: 0; |
|||
position: absolute; |
|||
right: 0; |
|||
top: 100%; |
|||
z-index: 1; |
|||
` |
|||
|
|||
const IconSelected = styled(Box).attrs({ |
|||
color: 'wallet', |
|||
alignItems: 'center', |
|||
justifyContent: 'center', |
|||
})` |
|||
height: 12px; |
|||
width: 12px; |
|||
opacity: ${p => (p.selected ? 1 : 0)}; |
|||
` |
|||
|
|||
const AngleDown = props => ( |
|||
<Box color="grey" alignItems="center" justifyContent="center" {...props}> |
|||
<svg viewBox="0 0 16 16" width="16" height="16"> |
|||
<path |
|||
fill="currentColor" |
|||
d="M7.70785815 10.86875l-5.08670521-4.5875c-.16153725-.146875-.16153725-.384375 0-.53125l.68051867-.61875c.16153726-.146875.42274645-.146875.58428371 0L8 8.834375l4.1140447-3.703125c.1615372-.146875.4227464-.146875.5842837 0l.6805187.61875c.1615372.146875.1615372.384375 0 .53125l-5.08670525 4.5875c-.16153726.146875-.42274644.146875-.5842837 0z" |
|||
/> |
|||
</svg> |
|||
</Box> |
|||
) |
|||
|
|||
const renderSelectedItem = ({ selectedItem, renderSelected, placeholder }: any) => |
|||
selectedItem && renderSelected ? ( |
|||
renderSelected(selectedItem) |
|||
) : ( |
|||
<Text color="fog">{placeholder}</Text> |
|||
) |
|||
|
|||
class LegacySelect extends PureComponent<Props> { |
|||
static defaultProps = { |
|||
bg: undefined, |
|||
disabled: false, |
|||
small: false, |
|||
fakeFocusRight: false, |
|||
flatLeft: false, |
|||
flatRight: false, |
|||
itemToString: (item: Object) => item && item.name, |
|||
keyProp: undefined, |
|||
maxHeight: 300, |
|||
} |
|||
|
|||
_scrollToSelectedItem = true |
|||
_oldHighlightedIndex = 0 |
|||
_useKeyboard = false |
|||
_children = {} |
|||
|
|||
renderItems = (items: Array<Object>, selectedItem: any, downshiftProps: Object) => { |
|||
const { renderItem, maxHeight, keyProp, t } = this.props |
|||
const { getItemProps, highlightedIndex } = downshiftProps |
|||
|
|||
const selectedItemIndex = items.indexOf(selectedItem) |
|||
|
|||
return ( |
|||
<Dropdown> |
|||
{items.length ? ( |
|||
<GrowScroll |
|||
maxHeight={maxHeight} |
|||
onUpdate={scrollbar => { |
|||
const currentHighlighted = this._children[highlightedIndex] |
|||
const currentSelectedItem = this._children[selectedItemIndex] |
|||
|
|||
if (this._useKeyboard && currentHighlighted) { |
|||
scrollbar.scrollIntoView(currentHighlighted, { |
|||
alignToTop: highlightedIndex < this._oldHighlightedIndex, |
|||
offsetTop: -1, |
|||
onlyScrollIfNeeded: true, |
|||
}) |
|||
} else if (this._scrollToSelectedItem && currentSelectedItem) { |
|||
window.requestAnimationFrame(() => |
|||
scrollbar.scrollIntoView(currentSelectedItem, { |
|||
offsetTop: -1, |
|||
}), |
|||
) |
|||
|
|||
this._scrollToSelectedItem = false |
|||
} |
|||
|
|||
this._oldHighlightedIndex = highlightedIndex |
|||
}} |
|||
> |
|||
{items.map((item, i) => ( |
|||
<Box |
|||
key={keyProp ? item[keyProp] : item.key} |
|||
innerRef={n => (this._children[i] = n)} |
|||
{...getItemProps({ item })} |
|||
> |
|||
<Item |
|||
first={i === 0} |
|||
last={i === items.length - 1} |
|||
highlighted={i === highlightedIndex} |
|||
selected={selectedItem === item} |
|||
horizontal |
|||
flow={3} |
|||
> |
|||
<Box grow> |
|||
{renderItem ? ( |
|||
renderItem(item) |
|||
) : ( |
|||
<span>{item.name_highlight || item.name}</span> |
|||
)} |
|||
</Box> |
|||
<Box> |
|||
<IconSelected selected={selectedItem === item}> |
|||
<IconCheck size={12} /> |
|||
</IconSelected> |
|||
</Box> |
|||
</Item> |
|||
</Box> |
|||
))} |
|||
</GrowScroll> |
|||
) : ( |
|||
<Box> |
|||
<Item>{t('app:error.noResults')}</Item> |
|||
</Box> |
|||
)} |
|||
</Dropdown> |
|||
) |
|||
} |
|||
|
|||
render() { |
|||
const { |
|||
disabled, |
|||
fakeFocusRight, |
|||
flatLeft, |
|||
flatRight, |
|||
fuseOptions, |
|||
highlight, |
|||
items, |
|||
itemToString, |
|||
onChange, |
|||
placeholder, |
|||
renderHighlight, |
|||
renderSelected, |
|||
searchable, |
|||
value, |
|||
small, |
|||
...props |
|||
} = this.props |
|||
|
|||
return ( |
|||
<Downshift |
|||
selectedItem={value} |
|||
itemToString={itemToString} |
|||
onChange={onChange} |
|||
render={({ |
|||
getInputProps, |
|||
getToggleButtonProps, |
|||
getRootProps, |
|||
isOpen, |
|||
inputValue, |
|||
openMenu, |
|||
selectedItem, |
|||
...downshiftProps |
|||
}) => { |
|||
if (!isOpen) { |
|||
this._scrollToSelectedItem = true |
|||
} |
|||
|
|||
if (disabled) { |
|||
return ( |
|||
<Container {...getRootProps({ refKey: 'innerRef' })}> |
|||
<TriggerBtn disabled bg={props.bg} tabIndex={0} small={small}> |
|||
{renderSelectedItem({ selectedItem, renderSelected, placeholder })} |
|||
</TriggerBtn> |
|||
</Container> |
|||
) |
|||
} |
|||
|
|||
return ( |
|||
<Container |
|||
{...getRootProps({ refKey: 'innerRef' })} |
|||
{...props} |
|||
horizontal |
|||
onKeyDown={() => (this._useKeyboard = true)} |
|||
onKeyUp={() => (this._useKeyboard = false)} |
|||
> |
|||
{searchable ? ( |
|||
<Box grow> |
|||
<Input |
|||
small |
|||
keepEvent |
|||
onClick={openMenu} |
|||
renderRight={<AngleDown mr={2} />} |
|||
{...getInputProps({ placeholder })} |
|||
/> |
|||
</Box> |
|||
) : ( |
|||
<TriggerBtn |
|||
{...getToggleButtonProps()} |
|||
bg={props.bg} |
|||
fakeFocusRight={fakeFocusRight} |
|||
flatLeft={flatLeft} |
|||
flatRight={flatRight} |
|||
tabIndex={0} |
|||
small={small} |
|||
> |
|||
<Box grow> |
|||
{renderSelectedItem({ selectedItem, renderSelected, placeholder })} |
|||
</Box> |
|||
<AngleDown mr={-1} /> |
|||
</TriggerBtn> |
|||
)} |
|||
<div hidden={!isOpen}> |
|||
{searchable ? ( |
|||
<Search |
|||
value={inputValue} |
|||
items={items} |
|||
fuseOptions={fuseOptions} |
|||
highlight={highlight} |
|||
renderHighlight={renderHighlight} |
|||
render={items => this.renderItems(items, selectedItem, downshiftProps)} |
|||
/> |
|||
) : ( |
|||
this.renderItems(items, selectedItem, downshiftProps) |
|||
)} |
|||
</div> |
|||
</Container> |
|||
) |
|||
}} |
|||
/> |
|||
) |
|||
} |
|||
} |
|||
|
|||
export default translate()(LegacySelect) |
@ -1,117 +0,0 @@ |
|||
// @flow
|
|||
|
|||
import React, { PureComponent } from 'react' |
|||
import { storiesOf } from '@storybook/react' |
|||
import { boolean } from '@storybook/addon-knobs' |
|||
|
|||
import Box from 'components/base/Box' |
|||
import LegacySelect from 'components/base/LegacySelect' |
|||
import Text from 'components/base/Text' |
|||
|
|||
const stories = storiesOf('Components/base/LegacySelect', module) |
|||
|
|||
const itemsChessPlayers = [ |
|||
{ key: 'aleksandr-grichtchouk', name: 'Aleksandr Grichtchouk' }, |
|||
{ key: 'fabiano-caruana', name: 'Fabiano Caruana' }, |
|||
{ key: 'garry-kasparov', name: 'Garry Kasparov' }, |
|||
{ key: 'hikaru-nakamura', name: 'Hikaru Nakamura' }, |
|||
{ key: 'levon-aronian', name: 'Levon Aronian' }, |
|||
{ key: 'magnus-carlsen', name: 'Magnus Carlsen' }, |
|||
{ key: 'maxime-vachier-lagrave', name: 'Maxime Vachier-Lagrave' }, |
|||
{ key: 'shakhriyar-mamedyarov', name: 'Shakhriyar Mamedyarov' }, |
|||
{ key: 'veselin-topalov', name: 'Veselin Topalov' }, |
|||
{ key: 'viswanathan-anand', name: 'Viswanathan Anand' }, |
|||
{ key: 'vladimir-kramnik', name: 'Vladimir Kramnik' }, |
|||
] |
|||
|
|||
type State = { |
|||
item: Object | null, |
|||
} |
|||
|
|||
class Wrapper extends PureComponent<any, State> { |
|||
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 => ( |
|||
<LegacySelect |
|||
disabled={boolean('disabled', false)} |
|||
placeholder="Choose a chess player..." |
|||
items={itemsChessPlayers} |
|||
renderSelected={item => item.name} |
|||
onChange={onChange} |
|||
/> |
|||
)} |
|||
</Wrapper> |
|||
)) |
|||
|
|||
stories.add('searchable', () => ( |
|||
<LegacySelect |
|||
placeholder="Choose a chess player..." |
|||
items={itemsChessPlayers} |
|||
searchable |
|||
highlight |
|||
fuseOptions={{ keys: ['name'] }} |
|||
itemToString={item => (item ? item.name : '')} |
|||
renderHighlight={(text, key) => ( |
|||
<Text key={key} fontWeight="bold"> |
|||
{text} |
|||
</Text> |
|||
)} |
|||
/> |
|||
)) |
|||
|
|||
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', () => ( |
|||
<LegacySelect |
|||
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…
Reference in new issue