From 6fe00e7316f9eba7788b64e5485660404fa50792 Mon Sep 17 00:00:00 2001 From: leXuanNha Date: Fri, 10 Apr 2020 11:01:45 +0700 Subject: [PATCH] Feat/828 create tag component in elements (#866) * feat: #828 Add dropdown select component using rc-select * put tag component into formik and testing * fix lint Co-authored-by: Vu Nguyen --- packages/elements/package.json | 2 + .../__test__/__snapshots__/index.tsx.snap | 17 + .../DropdownSelect/__test__/index.tsx | 105 ++++++ .../dropdown-select.stories.tsx | 29 ++ .../src/components/DropdownSelect/index.scss | 330 ++++++++++++++++++ .../src/components/DropdownSelect/index.tsx | 40 +++ yarn.lock | 68 +++- 7 files changed, 590 insertions(+), 1 deletion(-) create mode 100644 packages/elements/src/components/DropdownSelect/__test__/__snapshots__/index.tsx.snap create mode 100644 packages/elements/src/components/DropdownSelect/__test__/index.tsx create mode 100644 packages/elements/src/components/DropdownSelect/dropdown-select.stories.tsx create mode 100644 packages/elements/src/components/DropdownSelect/index.scss create mode 100644 packages/elements/src/components/DropdownSelect/index.tsx diff --git a/packages/elements/package.json b/packages/elements/package.json index 1a3a11d800..495f91004a 100644 --- a/packages/elements/package.json +++ b/packages/elements/package.json @@ -39,6 +39,7 @@ ], "dependencies": { "@storybook/theming": "^5.2.8", + "@types/rc-select": "^5.9.34", "bulma": "^0.7.5", "formik": "^2.0.4", "gh-pages": "^2.2.0", @@ -49,6 +50,7 @@ "pell": "^1.0.6", "prop-types": "^15.7.2", "rc-notification": "^4.0.0", + "rc-select": "^10.1.8", "react-datasheet": "^1.4.0", "react-datepicker": "^2.9.6", "react-google-map": "^3.1.1", diff --git a/packages/elements/src/components/DropdownSelect/__test__/__snapshots__/index.tsx.snap b/packages/elements/src/components/DropdownSelect/__test__/__snapshots__/index.tsx.snap new file mode 100644 index 0000000000..d93a02e2aa --- /dev/null +++ b/packages/elements/src/components/DropdownSelect/__test__/__snapshots__/index.tsx.snap @@ -0,0 +1,17 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Dropdown-select should match a snapshot 1`] = ` +
+
+ + + +
+
+`; diff --git a/packages/elements/src/components/DropdownSelect/__test__/index.tsx b/packages/elements/src/components/DropdownSelect/__test__/index.tsx new file mode 100644 index 0000000000..ed5f33b16d --- /dev/null +++ b/packages/elements/src/components/DropdownSelect/__test__/index.tsx @@ -0,0 +1,105 @@ +import * as React from 'react' +import { shallow, mount } from 'enzyme' +import { DropdownSelect, DropdownSelectProps } from '../index' +import { Formik, Form, FormikErrors } from 'formik' +import toJson from 'enzyme-to-json' +import { act } from 'react-dom/test-utils' + +const mockedOptions = [ + { label: 'a', value: 'a' }, + { label: 'b', value: 'b' }, +] +const dropdownSelectProps: DropdownSelectProps = { + name: 'demo', + labelText: 'demo', +} + +const createFormikWrapper = () => { + const wrapper = mount( + + {() => ( +
+
+ +
+
+ )} +
, + ) + + return wrapper +} + +const ErrorFomrikComponent = () => { + return ( +
+ { + const errors: FormikErrors = { + demo: '', + } + if (values.demo === 'b') { + errors.demo = 'Required' + return errors + } + return errors + }} + initialValues={{ demo: 'a' }} + onSubmit={jest.fn()} + > + {() => ( +
+
+ +
+
+ )} +
+
+ ) +} + +describe('Dropdown-select', () => { + it('should match a snapshot', () => { + expect(toJson(shallow())).toMatchSnapshot() + }) + + describe('should work when integrating with Formik', () => { + it('Render error correctly', async () => { + const wrapper = mount() + const select = wrapper.find('Select') + await act(async () => { + select.simulate('change', { target: { name: 'demo', value: 'b' } }) + }) + + wrapper.update() + + expect(wrapper.find('Select').props().value).toBe('b') + }) + }) + + it('Render label correctly', () => { + const wrapper = createFormikWrapper() + const label = wrapper.find('label').first() + expect(label.text()).toBe('Demo') + }) + + it('Map value correctly from formik', async () => { + const wrapper = createFormikWrapper() + + await act(async () => { + wrapper.find('Select').simulate('change', { + target: { + name: 'demo', + value: 'a', + }, + }) + }) + wrapper.update() + expect(wrapper.find('Select').prop('value')).toEqual('a') + }) + + afterEach(() => { + jest.resetAllMocks() + }) +}) diff --git a/packages/elements/src/components/DropdownSelect/dropdown-select.stories.tsx b/packages/elements/src/components/DropdownSelect/dropdown-select.stories.tsx new file mode 100644 index 0000000000..efc702f990 --- /dev/null +++ b/packages/elements/src/components/DropdownSelect/dropdown-select.stories.tsx @@ -0,0 +1,29 @@ +import React from 'react' +import { Form, Formik } from 'formik' + +import { storiesOf } from '@storybook/react' +import { DropdownSelect } from '.' +import { action } from '@storybook/addon-actions' + +const options = [ + { value: 'jack', label: 'Jack' }, + { value: 'lucy', label: 'Lucy' }, + { value: 'yiminghe', label: 'Yiminghe' }, +] + +storiesOf('DropdownSelect', module).add('Primary', () => { + return ( +
+ { + action('Form Values' + values) + }} + > +
+ + +
+
+ ) +}) diff --git a/packages/elements/src/components/DropdownSelect/index.scss b/packages/elements/src/components/DropdownSelect/index.scss new file mode 100644 index 0000000000..b2d5657d07 --- /dev/null +++ b/packages/elements/src/components/DropdownSelect/index.scss @@ -0,0 +1,330 @@ +* { + box-sizing: border-box; +} + +.rc-select { + display: inline-block; + position: relative; + background-color: #fff; + border-color: #dbdbdb; + border-radius: 4px; + color: #363636; + border: 1px solid transparent; + font-size: 1rem; + line-height: 1.5; + padding-bottom: calc(0.375em - 1px); + padding-left: calc(0.625em - 1px); + padding-right: calc(0.625em - 1px); + padding-top: calc(0.375em - 1px); + position: relative; + vertical-align: top; + box-shadow: inset 0 1px 2px rgba(10,10,10,0.1); + max-width: 100%; + width: 100%; + height: auto; +} + +.rc-select-disabled { + cursor: not-allowed; + + input { + cursor: not-allowed; + } + + .rc-select-selector { + opacity: 0.3; + } +} + +.rc-select-show-arrow.rc-select-loading .rc-select-arrow-icon::after { + box-sizing: border-box; + width: 12px; + height: 12px; + border-radius: 100%; + border: 2px solid #999; + border-top-color: transparent; + border-bottom-color: transparent; + transform: none; + margin-top: 4px; + animation: rcSelectLoadingIcon 0.5s infinite; +} + +.rc-select .rc-select-selection-placeholder { + opacity: 0.4; +} + +.rc-select-single { + .rc-select-selector { + display: flex; + position: relative; + + .rc-select-selection-search, .rc-select-selection-search-input { + width: 100%; + } + + .rc-select-selection-item, .rc-select-selection-placeholder { + position: absolute; + top: 1px; + left: 3px; + pointer-events: none; + } + } + + &:not(.rc-select-customize-input) .rc-select-selector { + padding: 1px; + border: 1px solid #000; + + .rc-select-selection-search-input { + border: none; + outline: none; + background: white; + width: 100%; + } + } +} + +.rc-select-multiple .rc-select-selector { + display: flex; + flex-wrap: wrap; + padding: 1px; + cursor: text; + .rc-select-selection-item { + flex: none; + background: #f5f5f5; + border: 1px solid #f0f0f0; + border-radius: 4px; + margin-right: 2px; + padding: 0 8px; + .rc-select-selection-item-remove { + margin-left: 10px; + vertical-align: middle; + cursor: pointer; + .rc-select-selection-item-remove-icon { + font-size: 18px; + } + } + } + + .rc-select-selection-item-disabled { + cursor: not-allowed; + opacity: 0.5; + } + + .rc-select-selection-search { + position: relative; + } + + .rc-select-selection-search-input { + padding: 1px; + font-family: system-ui; + } + + .rc-select-selection-search-mirror { + padding: 1px; + font-family: system-ui; + position: absolute; + z-index: 999; + white-space: nowrap; + position: none; + left: 0; + top: 0; + visibility: hidden; + } + + .rc-select-selection-search-input { + border: none; + outline: none; + width: 100%; + height: 100%; + } +} + +.rc-select-allow-clear { + &.rc-select-multiple .rc-select-selector { + padding-right: 20px; + } + + .rc-select-clear { + position: absolute; + right: 20px; + top: 0; + } +} + +.rc-select-show-arrow { + &.rc-select-multiple .rc-select-selector { + padding-right: 20px; + } + + .rc-select-arrow { + pointer-events: none; + position: absolute; + right: 5px; + top: 0; + } + + .rc-select-arrow-icon::after { + content: ''; + border: 5px solid transparent; + width: 0; + height: 0; + display: inline-block; + border-top-color: #999; + transform: translateY(5px); + } +} + +.rc-select-focused .rc-select-selector { + border-color: blue !important; +} + +.rc-select-dropdown { + min-height: 100px; + position: absolute; + background: #fff; + box-shadow: 0 3px 6px -4px rgba(0,0,0,.12), 0 6px 16px 0 rgba(0,0,0,.08), 0 9px 28px 8px rgba(0,0,0,.05); +} + +.rc-select-dropdown-hidden { + display: none; +} + +.rc-select-item { + font-size: 16px; + line-height: 1.5; + padding: 4px 16px; +} + +.rc-select-item-group { + color: #999; + font-weight: bold; + font-size: 80%; +} + +.rc-select-item-option { + position: relative; +} + +.rc-select-item-option-grouped { + padding-left: 24px; +} + +.rc-select-item-option .rc-select-item-option-state { + position: absolute; + right: 0; + top: 4px; + pointer-events: none; +} + +.rc-select-item-option-active { + background: #f5f5f5; +} + +.rc-select-item-option-disabled { + color: #999; +} + +.rc-select-item-empty { + text-align: center; + color: #999; +} + +.rc-select-selection__choice-zoom { + transition: all 0.3s; +} + +.rc-select-selection__choice-zoom-appear { + opacity: 0; + transform: scale(0.5); + + &.rc-select-selection__choice-zoom-appear-active { + opacity: 1; + transform: scale(1); + } +} + +.rc-select-selection__choice-zoom-leave { + opacity: 1; + transform: scale(1); + + &.rc-select-selection__choice-zoom-leave-active { + opacity: 0; + transform: scale(0.5); + } +} + +.rc-select-dropdown-slide-up-enter, .rc-select-dropdown-slide-up-appear { + animation-duration: 0.3s; + animation-fill-mode: both; + transform-origin: 0 0; + opacity: 0; + animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1); + animation-play-state: paused; +} + +.rc-select-dropdown-slide-up-leave { + animation-duration: 0.3s; + animation-fill-mode: both; + transform-origin: 0 0; + opacity: 1; + animation-timing-function: cubic-bezier(0.6, 0.04, 0.98, 0.34); + animation-play-state: paused; +} + +.rc-select-dropdown-slide-up-enter.rc-select-dropdown-slide-up-enter-active.rc-select-dropdown-placement-bottomLeft, .rc-select-dropdown-slide-up-appear.rc-select-dropdown-slide-up-appear-active.rc-select-dropdown-placement-bottomLeft { + animation-name: rcSelectDropdownSlideUpIn; + animation-play-state: running; +} + +.rc-select-dropdown-slide-up-leave.rc-select-dropdown-slide-up-leave-active.rc-select-dropdown-placement-bottomLeft { + animation-name: rcSelectDropdownSlideUpOut; + animation-play-state: running; +} + +.rc-select-dropdown-slide-up-enter.rc-select-dropdown-slide-up-enter-active.rc-select-dropdown-placement-topLeft, .rc-select-dropdown-slide-up-appear.rc-select-dropdown-slide-up-appear-active.rc-select-dropdown-placement-topLeft { + animation-name: rcSelectDropdownSlideDownIn; + animation-play-state: running; +} + +.rc-select-dropdown-slide-up-leave.rc-select-dropdown-slide-up-leave-active.rc-select-dropdown-placement-topLeft { + animation-name: rcSelectDropdownSlideDownOut; + animation-play-state: running; +} + +@keyframes rcSelectDropdownSlideUpIn { + 0% { + opacity: 0; + transform-origin: 0% 0%; + transform: scaleY(0); + } + + 100% { + opacity: 1; + transform-origin: 0% 0%; + transform: scaleY(1); + } +} + +@keyframes rcSelectDropdownSlideUpOut { + 0% { + opacity: 1; + transform-origin: 0% 0%; + transform: scaleY(1); + } + + 100% { + opacity: 0; + transform-origin: 0% 0%; + transform: scaleY(0); + } +} + +@keyframes rcSelectLoadingIcon { + 0% { + transform: rotate(0); + } + + 100% { + transform: rotate(360deg); + } +} \ No newline at end of file diff --git a/packages/elements/src/components/DropdownSelect/index.tsx b/packages/elements/src/components/DropdownSelect/index.tsx new file mode 100644 index 0000000000..ac750416c0 --- /dev/null +++ b/packages/elements/src/components/DropdownSelect/index.tsx @@ -0,0 +1,40 @@ +import * as React from 'react' +import Select, { Option, SelectProps } from 'rc-select' +import { Field, FieldProps } from 'formik' +import './index.scss' + +const handleChangeOption = field => value => { + field.onChange({ target: { value: value, name: field.name } }) +} + +export interface DropdownSelectProps { + labelText: string + name: string +} + +export const DropdownSelect: React.FC = props => { + return ( +
+
+ + {({ field }: FieldProps) => { + return ( +
+ + +
+ ) + }} +
+
+
+ ) +} + +export default DropdownSelect diff --git a/yarn.lock b/yarn.lock index 1e78a6410b..4dfe734aba 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4985,6 +4985,13 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c" integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA== +"@types/rc-select@^5.9.34": + version "5.9.34" + resolved "https://registry.yarnpkg.com/@types/rc-select/-/rc-select-5.9.34.tgz#79a1f8e4b5a7613690d771fec4773fdce46c0038" + integrity sha512-h3x9yEKywj4xw54+gNp1PaCVmkLXZxQj8M1rZz9eLpFVjtfjdFbjPYOYcM9qAW5ZCpPgWGQ+dBZfcXhYzsITNA== + dependencies: + "@types/react" "*" + "@types/reach__router@^1.2.3": version "1.3.0" resolved "https://registry.yarnpkg.com/@types/reach__router/-/reach__router-1.3.0.tgz#4c05a947ccecca05c72bb335a0f7bb43fec12446" @@ -10207,6 +10214,11 @@ dom-accessibility-api@^0.3.0: resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.3.0.tgz#511e5993dd673b97c87ea47dba0e3892f7e0c983" integrity sha512-PzwHEmsRP3IGY4gv/Ug+rMeaTIyTJvadCb+ujYXYeIylbHJezIyNToe8KfEgHTCEYyC+/bUghYOGg8yMGlZ6vA== +dom-align@^1.7.0: + version "1.11.1" + resolved "https://registry.yarnpkg.com/dom-align/-/dom-align-1.11.1.tgz#7592be99a660a36cdedc1d6eeb22b8109d758cae" + integrity sha512-hN42DmUgtweBx0iBjDLO4WtKOMcK8yBmPx/fgdsgQadLuzPu/8co3oLdK5yMmeM/vnUd3yDyV6qV8/NzxBexQg== + dom-converter@^0.2: version "0.2.0" resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" @@ -20248,7 +20260,17 @@ raw-loader@^3.1.0: loader-utils "^1.1.0" schema-utils "^2.0.1" -rc-animate@2.x: +rc-align@^3.0.0-rc.0: + version "3.0.0-rc.1" + resolved "https://registry.yarnpkg.com/rc-align/-/rc-align-3.0.0-rc.1.tgz#32d1fac860d12bb85e9b8cafbbdef79f3f537674" + integrity sha512-GbofumhCUb7SxP410j/fbtR2M9Zml+eoZSdaliZh6R3NhfEj5zP4jcO3HG3S9C9KIcXQQtd/cwVHkb9Y0KU7Hg== + dependencies: + classnames "2.x" + dom-align "^1.7.0" + rc-util "^4.12.0" + resize-observer-polyfill "^1.5.1" + +rc-animate@2.x, rc-animate@^2.10.0, rc-animate@^2.10.2: version "2.10.3" resolved "https://registry.yarnpkg.com/rc-animate/-/rc-animate-2.10.3.tgz#163d5e29281a4ff82d53ee7918eeeac856b756f9" integrity sha512-A9qQ5Y8BLlM7EhuCO3fWb/dChndlbWtY/P5QvPqBU7h4r5Q2QsvsbpTGgdYZATRDZbTRnJXXfVk9UtlyS7MBLg== @@ -20270,6 +20292,30 @@ rc-notification@^4.0.0: rc-animate "2.x" rc-util "^4.0.4" +rc-select@^10.1.8: + version "10.1.8" + resolved "https://registry.yarnpkg.com/rc-select/-/rc-select-10.1.8.tgz#cb6f95a87e42ab4d32006eb96d44fd3adfa08147" + integrity sha512-XEg9As5kOM6d8l27j6mZO2eet/aOrymIiP+Rmr6LMl2I6LBgElJnxB/CL/qAhrnQrUD2KqBzdXq3zjC7I/Ltdw== + dependencies: + classnames "2.x" + rc-animate "^2.10.0" + rc-trigger "^4.0.0" + rc-util "^4.20.0" + rc-virtual-list "^1.1.2" + warning "^4.0.3" + +rc-trigger@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/rc-trigger/-/rc-trigger-4.0.2.tgz#42fe7bdb6a5b34035e20fa9ebfad69ec948b56be" + integrity sha512-to5S1NhK10rWHIgQpoQdwIhuDc2Ok4R4/dh5NLrDt6C+gqkohsdBCYiPk97Z+NwGhRU8N+dbf251bivX8DkzQg== + dependencies: + classnames "^2.2.6" + prop-types "15.x" + raf "^3.4.1" + rc-align "^3.0.0-rc.0" + rc-animate "^2.10.2" + rc-util "^4.20.0" + rc-util@^4.0.4, rc-util@^4.15.3: version "4.20.1" resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-4.20.1.tgz#a5976eabfc3198ed9b8e79ffb8c53c231db36e77" @@ -20282,6 +20328,26 @@ rc-util@^4.0.4, rc-util@^4.15.3: react-lifecycles-compat "^3.0.4" shallowequal "^1.1.0" +rc-util@^4.12.0, rc-util@^4.20.0, rc-util@^4.8.0: + version "4.20.3" + resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-4.20.3.tgz#c4d4ee6171cf685dc75572752a764310325888d3" + integrity sha512-NBBc9Ad5yGAVTp4jV+pD7tXQGqHxGM2onPSZFyVoJ5fuvRF+ZgzSjZ6RXLPE0pVVISRJ07h+APgLJPBcAeZQlg== + dependencies: + add-dom-event-listener "^1.1.0" + prop-types "^15.5.10" + react-is "^16.12.0" + react-lifecycles-compat "^3.0.4" + shallowequal "^1.1.0" + +rc-virtual-list@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/rc-virtual-list/-/rc-virtual-list-1.1.2.tgz#fe3da1136b3ce612b37891fc2cf43447c8a40b2f" + integrity sha512-+WwxrtmBta7vcPCty7MtgilBmbxSGwN28Y8o+MG3GkHZccV0tXT+PLnAB+5WOjhhH10iFq+pzviRcXgcZ1x4OA== + dependencies: + classnames "^2.2.6" + raf "^3.4.1" + rc-util "^4.8.0" + rc@^1.0.1, rc@^1.1.6, rc@^1.2.7, rc@^1.2.8: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"