From 55f467574df3db3c462ef805471de77c2f53f753 Mon Sep 17 00:00:00 2001 From: meriadec Date: Thu, 31 May 2018 09:00:02 +0200 Subject: [PATCH] Create the new Select component --- package.json | 1 + src/components/base/Select/createRenderers.js | 71 ++++++++++++ src/components/base/Select/createStyles.js | 65 +++++++++++ src/components/base/Select/customRenders.js | 0 src/components/base/Select/index.js | 83 ++++++++++++++ src/components/base/Select/stories.js | 96 ++++++++++++++++ src/components/base/Select/style.js | 62 ++++++++++ src/styles/global.js | 18 +++ yarn.lock | 108 +++++++++++++++++- 9 files changed, 499 insertions(+), 5 deletions(-) create mode 100644 src/components/base/Select/createRenderers.js create mode 100644 src/components/base/Select/createStyles.js create mode 100644 src/components/base/Select/customRenders.js create mode 100644 src/components/base/Select/index.js create mode 100644 src/components/base/Select/stories.js create mode 100644 src/components/base/Select/style.js diff --git a/package.json b/package.json index 9f2782ee..03915d66 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,7 @@ "react-router": "^4.2.0", "react-router-dom": "^4.2.2", "react-router-redux": "5.0.0-alpha.9", + "react-select": "2.0.0-beta.6", "react-smooth-scrollbar": "^8.0.6", "react-spring": "^4.2.1", "redux": "^4.0.0", diff --git a/src/components/base/Select/createRenderers.js b/src/components/base/Select/createRenderers.js new file mode 100644 index 00000000..6292c41b --- /dev/null +++ b/src/components/base/Select/createRenderers.js @@ -0,0 +1,71 @@ +// @flow + +import React from 'react' +import styled from 'styled-components' +import { components } from 'react-select' + +import type { OptionProps } from 'react-select/lib/types' + +import Box from 'components/base/Box' +import IconCheck from 'icons/Check' +import IconAngleDown from 'icons/AngleDown' +import IconCross from 'icons/Cross' + +import type { Option } from './index' + +export default ({ + renderOption, + renderValue, +}: { + renderOption: Option => Node, + renderValue: Option => Node, +}) => ({ + ...STYLES_OVERRIDE, + Option: (props: OptionProps) => { + const { data, isSelected } = props + return ( + + + {renderOption ? renderOption(props) : data.label} + {isSelected && ( + + + + )} + + + ) + }, + SingleValue: (props: OptionProps) => { + const { data } = props + return ( + + {renderValue ? renderValue(props) : data.label} + + ) + }, +}) + +const STYLES_OVERRIDE = { + DropdownIndicator: (props: OptionProps) => ( + + + + ), + ClearIndicator: (props: OptionProps) => ( + + + + ), +} + +const CheckContainer = styled(Box).attrs({ + align: 'center', + justify: 'center', +})` + position: absolute; + top: 0; + right: 0; + bottom: 0; + width: 10px; +` diff --git a/src/components/base/Select/createStyles.js b/src/components/base/Select/createStyles.js new file mode 100644 index 00000000..0760bd74 --- /dev/null +++ b/src/components/base/Select/createStyles.js @@ -0,0 +1,65 @@ +// @flow + +import { colors } from 'styles/theme' +import { ff } from 'styles/helpers' + +export default ({ width, minWidth }: { width: number, minWidth: number }) => ({ + control: (styles: Object, { isFocused }: Object) => ({ + ...styles, + width, + minWidth, + ...ff('Open Sans|SemiBold'), + height: 40, + backgroundColor: 'white', + cursor: 'pointer', + ...(isFocused + ? { + borderColor: colors.wallet, + boxShadow: 'rgba(0, 0, 0, 0.05) 0 2px 2px', + } + : {}), + }), + valueContainer: (styles: Object) => ({ + ...styles, + paddingLeft: 15, + color: colors.graphite, + }), + indicatorSeparator: (styles: Object) => ({ + ...styles, + background: 'none', + }), + option: (styles: Object, { isFocused, isSelected }: Object) => ({ + ...styles, + ...ff('Open Sans|Regular'), + color: colors.dark, + padding: '10px 15px 10px 15px', + ...(isFocused + ? { + background: colors.lightGrey, + color: colors.dark, + } + : {}), + ...(isSelected + ? { + background: 'unset !important', + ...ff('Open Sans|SemiBold'), + } + : { + cursor: 'pointer', + }), + }), + menu: (styles: Object) => ({ + ...styles, + border: `1px solid ${colors.fog}`, + boxShadow: 'rgba(0, 0, 0, 0.05) 0 2px 2px', + }), + menuList: (styles: Object) => ({ + ...styles, + background: 'white', + borderRadius: 3, + }), + container: (styles: Object) => ({ + ...styles, + fontSize: 13, + }), +}) diff --git a/src/components/base/Select/customRenders.js b/src/components/base/Select/customRenders.js new file mode 100644 index 00000000..e69de29b diff --git a/src/components/base/Select/index.js b/src/components/base/Select/index.js new file mode 100644 index 00000000..5b458bd0 --- /dev/null +++ b/src/components/base/Select/index.js @@ -0,0 +1,83 @@ +// @flow + +import React, { Component } from 'react' +import ReactSelect from 'react-select' +import { translate } from 'react-i18next' + +import createStyles from './createStyles' +import createRenderers from './createRenderers' + +type Props = { + // required + value: ?Option, + options: Option[], + onChange: Option => void, + + // custom renders + renderOption: Option => Node, + renderValue: Option => Node, + + // optional + placeholder?: string, + isClearable?: boolean, + isDisabled?: boolean, + isLoading?: boolean, + isSearchable?: boolean, + width: number, + minWidth: number, +} + +export type Option = { + value: 'string', + label: 'string', + data: any, +} + +class Select extends Component { + handleChange = (value, { action }) => { + const { onChange } = this.props + if (action === 'select-option') { + onChange(value) + } + } + + render() { + const { + value, + isClearable, + isSearchable, + isDisabled, + isLoading, + placeholder, + options, + renderOption, + renderValue, + width, + minWidth, + ...props + } = this.props + + return ( + + ) + } +} + +export default translate()(Select) diff --git a/src/components/base/Select/stories.js b/src/components/base/Select/stories.js new file mode 100644 index 00000000..c48e914c --- /dev/null +++ b/src/components/base/Select/stories.js @@ -0,0 +1,96 @@ +// @flow + +import React, { PureComponent } from 'react' +import { storiesOf } from '@storybook/react' +import { boolean } from '@storybook/addon-knobs' + +import Box from 'components/base/Box' +import Select from 'components/base/Select' + +const stories = storiesOf('Components/base/Select', module) + +const itemsChessPlayers = [ + { value: 'aleksandr-grichtchouk', label: 'Aleksandr Grichtchouk' }, + { value: 'fabiano-caruana', label: 'Fabiano Caruana' }, + { value: 'garry-kasparov', label: 'Garry Kasparov' }, + { value: 'hikaru-nakamura', label: 'Hikaru Nakamura' }, + { value: 'levon-aronian', label: 'Levon Aronian' }, + { value: 'magnus-carlsen', label: 'Magnus Carlsen' }, + { value: 'maxime-vachier-lagrave', label: 'Maxime Vachier-Lagrave' }, + { value: 'shakhriyar-mamedyarov', label: 'Shakhriyar Mamedyarov' }, + { value: 'veselin-topalov', label: 'Veselin Topalov' }, + { value: 'viswanathan-anand', label: 'Viswanathan Anand' }, + { value: 'vladimir-kramnik', label: 'Vladimir Kramnik' }, +] + +type State = { + item: Object | null, +} + +stories.add('basic', () => ( + + {onChange => ( + ( + + + {item.label} + + )} + /> + )} + +)) + +class Wrapper extends PureComponent { + 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)}
+            
+
+ )} +
+ ) + } +} diff --git a/src/components/base/Select/style.js b/src/components/base/Select/style.js new file mode 100644 index 00000000..e7b4dd32 --- /dev/null +++ b/src/components/base/Select/style.js @@ -0,0 +1,62 @@ +import { colors } from 'styles/theme' +import { ff } from 'styles/helpers' + +export default { + control: (styles, { isFocused }) => ({ + ...styles, + ...ff('Open Sans|SemiBold'), + height: 40, + backgroundColor: 'white', + cursor: 'pointer', + ...(isFocused + ? { + borderColor: colors.wallet, + boxShadow: 'rgba(0, 0, 0, 0.05) 0 2px 2px', + } + : {}), + }), + valueContainer: styles => ({ + ...styles, + paddingLeft: 15, + color: colors.graphite, + }), + indicatorSeparator: styles => ({ + ...styles, + background: 'none', + }), + option: (styles, { isFocused, isSelected }) => ({ + ...styles, + ...ff('Open Sans|Regular'), + color: colors.dark, + padding: '10px 15px 10px 15px', + ...(isFocused + ? { + background: colors.lightGrey, + color: colors.dark, + } + : {}), + ...(isSelected + ? { + background: 'unset !important', + ...ff('Open Sans|SemiBold'), + } + : { + cursor: 'pointer', + }), + }), + menu: styles => ({ + ...styles, + border: `1px solid ${colors.fog}`, + boxShadow: 'rgba(0, 0, 0, 0.05) 0 2px 2px', + }), + menuList: styles => ({ + ...styles, + background: 'white', + borderRadius: 3, + overflow: 'hidden', + }), + container: styles => ({ + ...styles, + fontSize: 13, + }), +} diff --git a/src/styles/global.js b/src/styles/global.js index f399758f..aebc15b9 100644 --- a/src/styles/global.js +++ b/src/styles/global.js @@ -103,6 +103,24 @@ injectGlobal` fill: ${colors.dark}; } + .select__control:hover, .select__control-is-focused { + border-color: ${colors.fog}; + } + + .select__single-value { + color: inherit !important; + right: 0; + left: 15px; + } + + .select__placeholder { + color ${colors.fog} !important; + } + + .select__option:active { + background: ${colors.lightGrey} !important; + } + ::selection { background: ${rgba(colors.wallet, 0.1)}; } diff --git a/yarn.lock b/yarn.lock index a6b1bdc6..75240a0e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -237,6 +237,13 @@ dependencies: "@babel/types" "7.0.0-beta.48" +"@babel/helper-module-imports@7.0.0-beta.32": + version "7.0.0-beta.32" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.0.0-beta.32.tgz#8126fc024107c226879841b973677a4f4e510a03" + dependencies: + "@babel/types" "7.0.0-beta.32" + lodash "^4.2.0" + "@babel/helper-module-imports@7.0.0-beta.35": version "7.0.0-beta.35" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.0.0-beta.35.tgz#308e350e731752cdb4d0f058df1d704925c64e0a" @@ -1386,6 +1393,14 @@ invariant "^2.2.0" lodash "^4.17.5" +"@babel/types@7.0.0-beta.32": + version "7.0.0-beta.32" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.0.0-beta.32.tgz#c317d0ecc89297b80bbcb2f50608e31f6452a5ff" + dependencies: + esutils "^2.0.2" + lodash "^4.2.0" + to-fast-properties "^2.0.0" + "@babel/types@7.0.0-beta.35": version "7.0.0-beta.35" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.0.0-beta.35.tgz#cf933a9a9a38484ca724b335b88d83726d5ab960" @@ -1418,6 +1433,22 @@ lodash "^4.17.5" to-fast-properties "^2.0.0" +"@emotion/hash@^0.6.2": + version "0.6.3" + resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.6.3.tgz#0e7a5604626fc6c6d4ac4061a2f5ac80d50262a4" + +"@emotion/memoize@^0.6.1": + version "0.6.2" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.6.2.tgz#138e00b332d519b4e307bded6159e5ba48aba3ae" + +"@emotion/stylis@^0.6.5": + version "0.6.8" + resolved "https://registry.yarnpkg.com/@emotion/stylis/-/stylis-0.6.8.tgz#6ad4e8d32b19b440efa4481bbbcb98a8c12765bb" + +"@emotion/unitless@^0.6.2": + version "0.6.3" + resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.6.3.tgz#65682e68a82701c70eefb38d7f941a2c0bfa90de" + "@ledgerhq/hw-app-btc@^4.12.0", "@ledgerhq/hw-app-btc@^4.7.3": version "4.12.0" resolved "https://registry.yarnpkg.com/@ledgerhq/hw-app-btc/-/hw-app-btc-4.12.0.tgz#deae3200dcb6eb196f05a7357c5499f3920e6c6f" @@ -2594,6 +2625,22 @@ babel-plugin-component@^1.1.1: dependencies: "@babel/helper-module-imports" "7.0.0-beta.35" +babel-plugin-emotion@^9.1.2: + version "9.1.2" + resolved "https://registry.yarnpkg.com/babel-plugin-emotion/-/babel-plugin-emotion-9.1.2.tgz#e26b313fa0fecd0f2cc07b1e4ef05da167e4f740" + dependencies: + "@babel/helper-module-imports" "7.0.0-beta.32" + "@emotion/hash" "^0.6.2" + "@emotion/memoize" "^0.6.1" + "@emotion/stylis" "^0.6.5" + babel-plugin-macros "^2.0.0" + babel-plugin-syntax-jsx "^6.18.0" + convert-source-map "^1.5.0" + find-root "^1.1.0" + mkdirp "^0.5.1" + source-map "^0.5.7" + touch "^1.0.0" + babel-plugin-istanbul@^4.1.5: version "4.1.6" resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz#36c59b2192efce81c5b378321b74175add1c9a45" @@ -2607,7 +2654,7 @@ babel-plugin-jest-hoist@^22.4.4: version "22.4.4" resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-22.4.4.tgz#b9851906eab34c7bf6f8c895a2b08bea1a844c0b" -babel-plugin-macros@^2.2.0: +babel-plugin-macros@^2.0.0, babel-plugin-macros@^2.2.0: version "2.2.1" resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-2.2.1.tgz#7cc0f84735aa86f776b51860793a98928f43a7fa" dependencies: @@ -2752,7 +2799,7 @@ babel-plugin-syntax-function-bind@^6.8.0: version "6.13.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-function-bind/-/babel-plugin-syntax-function-bind-6.13.0.tgz#48c495f177bdf31a981e732f55adc0bdd2601f46" -babel-plugin-syntax-jsx@^6.3.13, babel-plugin-syntax-jsx@^6.8.0: +babel-plugin-syntax-jsx@^6.18.0, babel-plugin-syntax-jsx@^6.3.13, babel-plugin-syntax-jsx@^6.8.0: version "6.18.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946" @@ -4494,7 +4541,7 @@ content-type@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" -convert-source-map@^1.1.0, convert-source-map@^1.4.0, convert-source-map@^1.5.1: +convert-source-map@^1.1.0, convert-source-map@^1.4.0, convert-source-map@^1.5.0, convert-source-map@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5" @@ -4561,6 +4608,17 @@ create-ecdh@^4.0.0: bn.js "^4.1.0" elliptic "^6.0.0" +create-emotion@^9.1.3: + version "9.1.3" + resolved "https://registry.yarnpkg.com/create-emotion/-/create-emotion-9.1.3.tgz#a2e570415eb3e1ec7c42489ba55b2361baf0ed3f" + dependencies: + "@emotion/hash" "^0.6.2" + "@emotion/memoize" "^0.6.1" + "@emotion/stylis" "^0.6.5" + "@emotion/unitless" "^0.6.2" + stylis "^3.5.0" + stylis-rule-sheet "^0.0.10" + create-error-class@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6" @@ -5738,6 +5796,13 @@ emojis-list@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" +emotion@^9.1.2: + version "9.1.3" + resolved "https://registry.yarnpkg.com/emotion/-/emotion-9.1.3.tgz#e9b3e897ba3d5c0aff5628b0008a8993fa9c0937" + dependencies: + babel-plugin-emotion "^9.1.2" + create-emotion "^9.1.3" + encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" @@ -6516,6 +6581,10 @@ find-npm-prefix@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/find-npm-prefix/-/find-npm-prefix-1.0.2.tgz#8d8ce2c78b3b4b9e66c8acc6a37c231eb841cfdf" +find-root@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" + find-up@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" @@ -9659,6 +9728,12 @@ nopt@^4.0.1, nopt@~4.0.1: abbrev "1" osenv "^0.1.4" +nopt@~1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" + dependencies: + abbrev "1" + normalize-package-data@^2.0.0, normalize-package-data@^2.3.2, normalize-package-data@^2.3.4, normalize-package-data@^2.4.0, "normalize-package-data@~1.0.1 || ^2.0.0", normalize-package-data@~2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f" @@ -11136,7 +11211,7 @@ radium@^0.19.0: inline-style-prefixer "^2.0.5" prop-types "^15.5.8" -raf@^3.1.0: +raf@^3.1.0, raf@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.0.tgz#a28876881b4bc2ca9117d4138163ddb80f781575" dependencies: @@ -11310,6 +11385,12 @@ react-icons@^2.2.7: dependencies: react-icon-base "2.1.0" +react-input-autosize@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-2.2.1.tgz#ec428fa15b1592994fb5f9aa15bb1eb6baf420f8" + dependencies: + prop-types "^15.5.8" + react-inspector@^2.2.2: version "2.3.0" resolved "https://registry.yarnpkg.com/react-inspector/-/react-inspector-2.3.0.tgz#fc9c1d38ab687fc0d190dcaf133ae40158968fc8" @@ -11403,6 +11484,17 @@ react-router@^4.2.0: prop-types "^15.5.4" warning "^3.0.0" +react-select@2.0.0-beta.6: + version "2.0.0-beta.6" + resolved "https://registry.yarnpkg.com/react-select/-/react-select-2.0.0-beta.6.tgz#87ac27831f348cb9535dfd825534934adcfb7e97" + dependencies: + classnames "^2.2.5" + emotion "^9.1.2" + prop-types "^15.6.0" + raf "^3.4.0" + react-input-autosize "^2.2.1" + react-transition-group "^2.2.1" + react-smooth-scrollbar@^8.0.6: version "8.0.6" resolved "https://registry.yarnpkg.com/react-smooth-scrollbar/-/react-smooth-scrollbar-8.0.6.tgz#179072e6a547b3af589ea303c50fd86366275edc" @@ -11443,7 +11535,7 @@ react-textarea-autosize@^5.2.1: dependencies: prop-types "^15.6.0" -react-transition-group@^2.0.0: +react-transition-group@^2.0.0, react-transition-group@^2.2.1: version "2.3.1" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.3.1.tgz#31d611b33e143a5e0f2d94c348e026a0f3b474b6" dependencies: @@ -13318,6 +13410,12 @@ toposort@^1.0.0: version "1.0.7" resolved "https://registry.yarnpkg.com/toposort/-/toposort-1.0.7.tgz#2e68442d9f64ec720b8cc89e6443ac6caa950029" +touch@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/touch/-/touch-1.0.0.tgz#449cbe2dbae5a8c8038e30d71fa0ff464947c4de" + dependencies: + nopt "~1.0.10" + tough-cookie@>=2.3.3, tough-cookie@^2.3.3, tough-cookie@~2.3.3: version "2.3.4" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655"