From bd6dbcf3b45d20bcbca17256a02998a61c4da53c Mon Sep 17 00:00:00 2001 From: prabu-ssb Date: Fri, 17 Sep 2021 15:57:09 +0200 Subject: [PATCH 1/3] stateless examples --- package-lock.json | 13 +++ package.json | 1 + src/components/Input/indexStateless.jsx | 88 +++++++++++++++++ .../Input/indexUpdateOnValueChange.jsx | 94 +++++++++++++++++++ src/components/Input/input.story.jsx | 20 ++++ src/components/Input/inputStateless.story.jsx | 90 ++++++++++++++++++ .../Input/inputUpdateOnValueChange.story.jsx | 90 ++++++++++++++++++ 7 files changed, 396 insertions(+) create mode 100644 src/components/Input/indexStateless.jsx create mode 100644 src/components/Input/indexUpdateOnValueChange.jsx create mode 100644 src/components/Input/inputStateless.story.jsx create mode 100644 src/components/Input/inputUpdateOnValueChange.story.jsx diff --git a/package-lock.json b/package-lock.json index 568b78c5..dcad3a87 100644 --- a/package-lock.json +++ b/package-lock.json @@ -58,6 +58,7 @@ "rollup-plugin-uglify": "^6.0.4", "sass": "^1.26.11", "sass-loader": "^8.0.2", + "storybook-addon-state": "^1.0.3", "style-loader": "^1.2.1", "stylelint": "^13.12.0", "stylelint-config-recommended": "^3.0.0", @@ -28298,6 +28299,12 @@ "integrity": "sha512-7t+/wpKLanLzSnQPX8WAcuLCCeuSHoWdQuh9SB3xD0kNOM38DNf+0Oa+wmvxmYueRzkmh6IcdKFtvTa+ecgPDw==", "dev": true }, + "node_modules/storybook-addon-state": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/storybook-addon-state/-/storybook-addon-state-1.0.3.tgz", + "integrity": "sha512-0lpedSYu2xIlq4QK/8YH4Cuj6vt2p8iRCcJBb4JFxyLZ6RAMHFFkf072BKl4WrU1LfLq3/7SD4snvxRoTjvsvw==", + "dev": true + }, "node_modules/stream-browserify": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", @@ -55836,6 +55843,12 @@ "integrity": "sha512-7t+/wpKLanLzSnQPX8WAcuLCCeuSHoWdQuh9SB3xD0kNOM38DNf+0Oa+wmvxmYueRzkmh6IcdKFtvTa+ecgPDw==", "dev": true }, + "storybook-addon-state": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/storybook-addon-state/-/storybook-addon-state-1.0.3.tgz", + "integrity": "sha512-0lpedSYu2xIlq4QK/8YH4Cuj6vt2p8iRCcJBb4JFxyLZ6RAMHFFkf072BKl4WrU1LfLq3/7SD4snvxRoTjvsvw==", + "dev": true + }, "stream-browserify": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", diff --git a/package.json b/package.json index fb1c8e35..ddf9cb62 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,7 @@ "rollup-plugin-uglify": "^6.0.4", "sass": "^1.26.11", "sass-loader": "^8.0.2", + "storybook-addon-state": "^1.0.3", "style-loader": "^1.2.1", "stylelint": "^13.12.0", "stylelint-config-recommended": "^3.0.0", diff --git a/src/components/Input/indexStateless.jsx b/src/components/Input/indexStateless.jsx new file mode 100644 index 00000000..f362b88a --- /dev/null +++ b/src/components/Input/indexStateless.jsx @@ -0,0 +1,88 @@ +import React, { useState, useEffect } from 'react'; +import PropTypes from 'prop-types'; +import uuid from 'uuid/v4'; +import { Search } from 'react-feather'; +import InputError from '../InputError'; + +const Input = ({ + ariaLabel, ariaLabelSearchButton, className, disabled, error, errorMessage, handleChange, id, label, negative, placeholder, searchField, submitCallback, type, value, onFocus, onBlur, size, +}) => { + const inputId = id || uuid(); + + const handleInputChange = e => { + handleChange(e.target.value); + }; + + const handleKeyDown = e => { + if (e.key === 'Enter') { + submitCallback(value); + } + }; + + return ( +
+ {label && } +
+ handleInputChange(e)} + onFocus={onFocus} + onBlur={onBlur} + placeholder={placeholder} + aria-label={ariaLabel} + className={searchField || error ? ' with-icon' : ''} + onKeyDown={searchField ? e => handleKeyDown(e) : undefined} + /> + {searchField && ( + + )} +
+ {error && (errorMessage && ( + + ))} +
+ ); +}; + +Input.defaultProps = { + className: '', + disabled: false, + error: false, + handleChange: () => {}, + onFocus: () => {}, + onBlur: () => {}, + negative: false, + searchField: false, + submitCallback: () => {}, + type: 'text', + ariaLabelSearchButton: 'search', + value: '', +}; + +Input.propTypes = { + ariaLabel: PropTypes.string, + ariaLabelSearchButton: PropTypes.string, + className: PropTypes.string, + disabled: PropTypes.bool, + error: PropTypes.bool, + errorMessage: PropTypes.string, + handleChange: PropTypes.func, + onFocus: PropTypes.func, + onBlur: PropTypes.func, + id: PropTypes.string, + label: PropTypes.string, + negative: PropTypes.bool, + placeholder: PropTypes.string, + searchField: PropTypes.bool, + size: PropTypes.string, + submitCallback: PropTypes.func, + type: PropTypes.string, + value: PropTypes.string, +}; + +export default Input; diff --git a/src/components/Input/indexUpdateOnValueChange.jsx b/src/components/Input/indexUpdateOnValueChange.jsx new file mode 100644 index 00000000..d5205a0e --- /dev/null +++ b/src/components/Input/indexUpdateOnValueChange.jsx @@ -0,0 +1,94 @@ +import React, { useState, useEffect } from 'react'; +import PropTypes from 'prop-types'; +import uuid from 'uuid/v4'; +import { Search } from 'react-feather'; +import InputError from '../InputError'; + +const Input = ({ + ariaLabel, ariaLabelSearchButton, className, disabled, error, errorMessage, handleChange, id, label, negative, placeholder, searchField, submitCallback, type, value, onFocus, onBlur, size, +}) => { + const [inputValue, setValue] = useState(value); + const inputId = id || uuid(); + + useEffect(() => { + setValue(value); + }, [value]); + + const handleInputChange = e => { + setValue(e.target.value); + handleChange(e.target.value); + }; + + const handleKeyDown = e => { + if (e.key === 'Enter') { + submitCallback(inputValue); + } + }; + + return ( +
+ {label && } +
+ handleInputChange(e)} + onFocus={onFocus} + onBlur={onBlur} + placeholder={placeholder} + aria-label={ariaLabel} + className={searchField || error ? ' with-icon' : ''} + onKeyDown={searchField ? e => handleKeyDown(e) : undefined} + /> + {searchField && ( + + )} +
+ {error && (errorMessage && ( + + ))} +
+ ); +}; + +Input.defaultProps = { + className: '', + disabled: false, + error: false, + handleChange: () => {}, + onFocus: () => {}, + onBlur: () => {}, + negative: false, + searchField: false, + submitCallback: () => {}, + type: 'text', + ariaLabelSearchButton: 'search', + value: '', +}; + +Input.propTypes = { + ariaLabel: PropTypes.string, + ariaLabelSearchButton: PropTypes.string, + className: PropTypes.string, + disabled: PropTypes.bool, + error: PropTypes.bool, + errorMessage: PropTypes.string, + handleChange: PropTypes.func, + onFocus: PropTypes.func, + onBlur: PropTypes.func, + id: PropTypes.string, + label: PropTypes.string, + negative: PropTypes.bool, + placeholder: PropTypes.string, + searchField: PropTypes.bool, + size: PropTypes.string, + submitCallback: PropTypes.func, + type: PropTypes.string, + value: PropTypes.string, +}; + +export default Input; diff --git a/src/components/Input/input.story.jsx b/src/components/Input/input.story.jsx index 04b080db..c01e556d 100644 --- a/src/components/Input/input.story.jsx +++ b/src/components/Input/input.story.jsx @@ -1,6 +1,7 @@ import React from 'react'; import { storiesOf } from '@storybook/react'; import centered from '@storybook/addon-centered/react'; +import useState from 'storybook-addon-state'; import Input from './index'; let someValue = ''; @@ -13,6 +14,25 @@ const handleSubmit = e => { }; storiesOf('Input', module).addDecorator(centered) + .add('with state', () => { + /** + * Just an example to show Input component works fine when the app state + * (which supplies the prop value) changes. Here appValue is shared across + * two components and updating one reflects in the other + */ + const [appValue, setAppValue] = useState('appVal', ''); + + const updateValue = value => { + setAppValue(value); + }; + + return ( +
+ + +
+ ); + }) .add('Default', () => (
diff --git a/src/components/Input/inputStateless.story.jsx b/src/components/Input/inputStateless.story.jsx new file mode 100644 index 00000000..c01e556d --- /dev/null +++ b/src/components/Input/inputStateless.story.jsx @@ -0,0 +1,90 @@ +import React from 'react'; +import { storiesOf } from '@storybook/react'; +import centered from '@storybook/addon-centered/react'; +import useState from 'storybook-addon-state'; +import Input from './index'; + +let someValue = ''; +const handleChange = e => { + someValue = e; +}; + +const handleSubmit = e => { + console.log(e); +}; + +storiesOf('Input', module).addDecorator(centered) + .add('with state', () => { + /** + * Just an example to show Input component works fine when the app state + * (which supplies the prop value) changes. Here appValue is shared across + * two components and updating one reflects in the other + */ + const [appValue, setAppValue] = useState('appVal', ''); + + const updateValue = value => { + setAppValue(value); + }; + + return ( +
+ + +
+ ); + }) + .add('Default', () => ( +
+ +
+ )) + .add('Search field', () => ( +
+ +
+ )) + .add('With value', () => ( +
+ +
+ )) + .add('Disabled', () => ( +
+ +
+ )) + .add('Error', () => ( +
+ +
+ )) + .add('Negative', () => ( +
+ + + +
+ )) + .add('Large input', () => ( +
+ +
+ )); diff --git a/src/components/Input/inputUpdateOnValueChange.story.jsx b/src/components/Input/inputUpdateOnValueChange.story.jsx new file mode 100644 index 00000000..46cf857d --- /dev/null +++ b/src/components/Input/inputUpdateOnValueChange.story.jsx @@ -0,0 +1,90 @@ +import React from 'react'; +import { storiesOf } from '@storybook/react'; +import centered from '@storybook/addon-centered/react'; +import useState from 'storybook-addon-state'; +import Input from './indexUpdateOnValueChange'; + +let someValue = ''; +const handleChange = e => { + someValue = e; +}; + +const handleSubmit = e => { + console.log(e); +}; + +storiesOf('Input', module).addDecorator(centered) + .add('with state', () => { + /** + * Just an example to show Input component works fine when the app state + * (which supplies the prop value) changes. Here appValue is shared across + * two components and updating one reflects in the other + */ + const [appValue, setAppValue] = useState('appVal', ''); + + const updateValue = value => { + setAppValue(value); + }; + + return ( +
+ + +
+ ); + }) + .add('Default', () => ( +
+ +
+ )) + .add('Search field', () => ( +
+ +
+ )) + .add('With value', () => ( +
+ +
+ )) + .add('Disabled', () => ( +
+ +
+ )) + .add('Error', () => ( +
+ +
+ )) + .add('Negative', () => ( +
+ + + +
+ )) + .add('Large input', () => ( +
+ +
+ )); From abda360b07d94e0ffcaeb57e2506968941172339 Mon Sep 17 00:00:00 2001 From: prabu-ssb Date: Mon, 20 Sep 2021 12:04:39 +0200 Subject: [PATCH 2/3] remove unused imports --- src/components/Input/indexStateless.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Input/indexStateless.jsx b/src/components/Input/indexStateless.jsx index f362b88a..5f302d93 100644 --- a/src/components/Input/indexStateless.jsx +++ b/src/components/Input/indexStateless.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; import uuid from 'uuid/v4'; import { Search } from 'react-feather'; From 5d939b7a98ad075ad5ed913e1912377308ab3efa Mon Sep 17 00:00:00 2001 From: prabu-ssb Date: Mon, 20 Sep 2021 15:39:51 +0200 Subject: [PATCH 3/3] retain event handler semantics --- src/components/Input/indexStateless.jsx | 6 +----- src/components/Input/indexUpdateOnValueChange.jsx | 2 +- src/components/Input/inputStateless.story.jsx | 8 ++++---- src/components/Input/inputUpdateOnValueChange.story.jsx | 2 +- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/components/Input/indexStateless.jsx b/src/components/Input/indexStateless.jsx index 5f302d93..eb898a12 100644 --- a/src/components/Input/indexStateless.jsx +++ b/src/components/Input/indexStateless.jsx @@ -9,10 +9,6 @@ const Input = ({ }) => { const inputId = id || uuid(); - const handleInputChange = e => { - handleChange(e.target.value); - }; - const handleKeyDown = e => { if (e.key === 'Enter') { submitCallback(value); @@ -28,7 +24,7 @@ const Input = ({ disabled={disabled} type={type} value={value} - onChange={e => handleInputChange(e)} + onChange={handleChange} onFocus={onFocus} onBlur={onBlur} placeholder={placeholder} diff --git a/src/components/Input/indexUpdateOnValueChange.jsx b/src/components/Input/indexUpdateOnValueChange.jsx index d5205a0e..9951e257 100644 --- a/src/components/Input/indexUpdateOnValueChange.jsx +++ b/src/components/Input/indexUpdateOnValueChange.jsx @@ -16,7 +16,7 @@ const Input = ({ const handleInputChange = e => { setValue(e.target.value); - handleChange(e.target.value); + handleChange(e); }; const handleKeyDown = e => { diff --git a/src/components/Input/inputStateless.story.jsx b/src/components/Input/inputStateless.story.jsx index c01e556d..a80df653 100644 --- a/src/components/Input/inputStateless.story.jsx +++ b/src/components/Input/inputStateless.story.jsx @@ -6,11 +6,11 @@ import Input from './index'; let someValue = ''; const handleChange = e => { - someValue = e; + someValue = e.target.value; }; const handleSubmit = e => { - console.log(e); + console.log(e.target.value); }; storiesOf('Input', module).addDecorator(centered) @@ -22,8 +22,8 @@ storiesOf('Input', module).addDecorator(centered) */ const [appValue, setAppValue] = useState('appVal', ''); - const updateValue = value => { - setAppValue(value); + const updateValue = e => { + setAppValue(e.target.value); }; return ( diff --git a/src/components/Input/inputUpdateOnValueChange.story.jsx b/src/components/Input/inputUpdateOnValueChange.story.jsx index 46cf857d..5b94867f 100644 --- a/src/components/Input/inputUpdateOnValueChange.story.jsx +++ b/src/components/Input/inputUpdateOnValueChange.story.jsx @@ -6,7 +6,7 @@ import Input from './indexUpdateOnValueChange'; let someValue = ''; const handleChange = e => { - someValue = e; + someValue = e.target.value; }; const handleSubmit = e => {