diff options
Diffstat (limited to 'src/components/ui')
-rw-r--r-- | src/components/ui/ColorPickerInput.tsx | 18 | ||||
-rw-r--r-- | src/components/ui/Input.tsx | 170 | ||||
-rw-r--r-- | src/components/ui/input/index.tsx | 187 | ||||
-rw-r--r-- | src/components/ui/input/styles.ts | 1 |
4 files changed, 119 insertions, 257 deletions
diff --git a/src/components/ui/ColorPickerInput.tsx b/src/components/ui/ColorPickerInput.tsx index 7e3965331..da1fffb71 100644 --- a/src/components/ui/ColorPickerInput.tsx +++ b/src/components/ui/ColorPickerInput.tsx | |||
@@ -1,15 +1,25 @@ | |||
1 | import { ChangeEvent, Component, createRef, RefObject } from 'react'; | 1 | import { |
2 | ChangeEvent, | ||
3 | ChangeEventHandler, | ||
4 | Component, | ||
5 | createRef, | ||
6 | RefObject, | ||
7 | } from 'react'; | ||
2 | import { observer } from 'mobx-react'; | 8 | import { observer } from 'mobx-react'; |
3 | import classnames from 'classnames'; | 9 | import classnames from 'classnames'; |
4 | import { SliderPicker } from 'react-color'; | 10 | import { SliderPicker } from 'react-color'; |
11 | import { noop } from 'lodash'; | ||
5 | import { Field } from '../../@types/mobx-form.types'; | 12 | import { Field } from '../../@types/mobx-form.types'; |
6 | 13 | ||
7 | interface IProps { | 14 | interface IProps { |
8 | field: Field; | 15 | field: Field; |
9 | className?: string; | 16 | className?: string; |
10 | focus?: boolean; | 17 | focus?: boolean; |
18 | onChange: ChangeEventHandler<HTMLInputElement>; | ||
11 | } | 19 | } |
12 | 20 | ||
21 | // TODO - [TS DEBT] check if field can be spread instead of having it single field attribute in interface | ||
22 | @observer | ||
13 | class ColorPickerInput extends Component<IProps> { | 23 | class ColorPickerInput extends Component<IProps> { |
14 | private inputElement: RefObject<HTMLInputElement> = | 24 | private inputElement: RefObject<HTMLInputElement> = |
15 | createRef<HTMLInputElement>(); | 25 | createRef<HTMLInputElement>(); |
@@ -22,7 +32,9 @@ class ColorPickerInput extends Component<IProps> { | |||
22 | } | 32 | } |
23 | 33 | ||
24 | onChange(e: ChangeEvent<HTMLInputElement>) { | 34 | onChange(e: ChangeEvent<HTMLInputElement>) { |
25 | const { field } = this.props; | 35 | const { field, onChange = noop } = this.props; |
36 | |||
37 | onChange(e); | ||
26 | if (field.onChange) { | 38 | if (field.onChange) { |
27 | field.onChange(e); | 39 | field.onChange(e); |
28 | } | 40 | } |
@@ -87,4 +99,4 @@ class ColorPickerInput extends Component<IProps> { | |||
87 | } | 99 | } |
88 | } | 100 | } |
89 | 101 | ||
90 | export default observer(ColorPickerInput); | 102 | export default ColorPickerInput; |
diff --git a/src/components/ui/Input.tsx b/src/components/ui/Input.tsx deleted file mode 100644 index c22dc5838..000000000 --- a/src/components/ui/Input.tsx +++ /dev/null | |||
@@ -1,170 +0,0 @@ | |||
1 | import { | ||
2 | ChangeEvent, | ||
3 | ChangeEventHandler, | ||
4 | Component, | ||
5 | createRef, | ||
6 | ReactElement, | ||
7 | RefObject, | ||
8 | } from 'react'; | ||
9 | import { observer } from 'mobx-react'; | ||
10 | import classnames from 'classnames'; | ||
11 | import { defineMessages, injectIntl, WrappedComponentProps } from 'react-intl'; | ||
12 | import { mdiEye, mdiEyeOff } from '@mdi/js'; | ||
13 | import { noop } from 'lodash'; | ||
14 | import { scorePassword as scorePasswordFunc } from '../../helpers/password-helpers'; | ||
15 | import Icon from './icon'; | ||
16 | import { Field } from '../../@types/mobx-form.types'; | ||
17 | |||
18 | const messages = defineMessages({ | ||
19 | passwordToggle: { | ||
20 | id: 'settings.app.form.passwordToggle', | ||
21 | defaultMessage: 'Password toggle', | ||
22 | }, | ||
23 | }); | ||
24 | |||
25 | interface IProps extends WrappedComponentProps { | ||
26 | field: Field; | ||
27 | className?: string; | ||
28 | focus?: boolean; | ||
29 | showPasswordToggle?: boolean; | ||
30 | showLabel?: boolean; | ||
31 | scorePassword?: boolean; | ||
32 | prefix?: string; | ||
33 | suffix?: string; | ||
34 | placeholder?: string; | ||
35 | onChange?: ChangeEventHandler<HTMLInputElement>; | ||
36 | } | ||
37 | |||
38 | interface IState { | ||
39 | showPassword: boolean; | ||
40 | passwordScore: number; | ||
41 | } | ||
42 | |||
43 | // Can this file be merged into the './input/index.tsx' file? | ||
44 | @observer | ||
45 | class Input extends Component<IProps, IState> { | ||
46 | private inputElement: RefObject<HTMLInputElement> = | ||
47 | createRef<HTMLInputElement>(); | ||
48 | |||
49 | constructor(props: IProps) { | ||
50 | super(props); | ||
51 | |||
52 | this.state = { | ||
53 | showPassword: false, | ||
54 | passwordScore: 0, | ||
55 | }; | ||
56 | } | ||
57 | |||
58 | componentDidMount(): void { | ||
59 | const { focus = false } = this.props; | ||
60 | if (focus) { | ||
61 | this.focus(); | ||
62 | } | ||
63 | } | ||
64 | |||
65 | onChange(e: ChangeEvent<HTMLInputElement>): void { | ||
66 | const { field, scorePassword, onChange = noop } = this.props; | ||
67 | |||
68 | if (field.onChange) { | ||
69 | onChange(e); | ||
70 | field.onChange(e); | ||
71 | } | ||
72 | |||
73 | if (scorePassword) { | ||
74 | this.setState({ | ||
75 | passwordScore: scorePasswordFunc(field.value as string), | ||
76 | }); | ||
77 | } | ||
78 | } | ||
79 | |||
80 | focus() { | ||
81 | if (this.inputElement && this.inputElement.current) { | ||
82 | this.inputElement.current!.focus(); | ||
83 | } | ||
84 | } | ||
85 | |||
86 | render(): ReactElement { | ||
87 | const { | ||
88 | field, | ||
89 | className = null, | ||
90 | showPasswordToggle = false, | ||
91 | showLabel = true, | ||
92 | scorePassword = false, | ||
93 | prefix = '', | ||
94 | suffix = '', | ||
95 | intl, | ||
96 | } = this.props; | ||
97 | |||
98 | const { passwordScore } = this.state; | ||
99 | |||
100 | let { type } = field; | ||
101 | if (type === 'password' && this.state.showPassword) { | ||
102 | type = 'text'; | ||
103 | } | ||
104 | |||
105 | return ( | ||
106 | <div | ||
107 | className={classnames({ | ||
108 | 'franz-form__field': true, | ||
109 | 'has-error': field.error, | ||
110 | [`${className}`]: className, | ||
111 | })} | ||
112 | > | ||
113 | <div className="franz-form__input-wrapper"> | ||
114 | {prefix && <span className="franz-form__input-prefix">{prefix}</span>} | ||
115 | <input | ||
116 | id={field.id} | ||
117 | type={type} | ||
118 | className="franz-form__input" | ||
119 | name={field.name} | ||
120 | value={field.value} | ||
121 | placeholder={field.placeholder} | ||
122 | onChange={e => this.onChange(e)} | ||
123 | onBlur={field.onBlur} | ||
124 | onFocus={field.onFocus} | ||
125 | ref={this.inputElement} | ||
126 | disabled={field.disabled} | ||
127 | /> | ||
128 | {suffix && <span className="franz-form__input-suffix">{suffix}</span>} | ||
129 | {showPasswordToggle && ( | ||
130 | <button | ||
131 | type="button" | ||
132 | className={classnames({ | ||
133 | 'franz-form__input-modifier': true, | ||
134 | })} | ||
135 | onClick={() => | ||
136 | this.setState(prevState => ({ | ||
137 | showPassword: !prevState.showPassword, | ||
138 | })) | ||
139 | } | ||
140 | tabIndex={-1} | ||
141 | aria-label={intl.formatMessage(messages.passwordToggle)} | ||
142 | > | ||
143 | <Icon icon={this.state.showPassword ? mdiEye : mdiEyeOff} /> | ||
144 | </button> | ||
145 | )} | ||
146 | {scorePassword && ( | ||
147 | <div className="franz-form__password-score"> | ||
148 | {/* <progress value={this.state.passwordScore} max="100" /> */} | ||
149 | <meter | ||
150 | value={passwordScore < 5 ? 5 : passwordScore} | ||
151 | low={30} | ||
152 | high={75} | ||
153 | optimum={100} | ||
154 | max={100} | ||
155 | /> | ||
156 | </div> | ||
157 | )} | ||
158 | </div> | ||
159 | {field.label && showLabel && ( | ||
160 | <label className="franz-form__label" htmlFor={field.name}> | ||
161 | {field.label} | ||
162 | </label> | ||
163 | )} | ||
164 | {field.error && <div className="franz-form__error">{field.error}</div>} | ||
165 | </div> | ||
166 | ); | ||
167 | } | ||
168 | } | ||
169 | |||
170 | export default injectIntl(Input); | ||
diff --git a/src/components/ui/input/index.tsx b/src/components/ui/input/index.tsx index 2a36d7aa9..cb26c0ea4 100644 --- a/src/components/ui/input/index.tsx +++ b/src/components/ui/input/index.tsx | |||
@@ -1,5 +1,4 @@ | |||
1 | import { mdiEye, mdiEyeOff } from '@mdi/js'; | 1 | import { mdiEye, mdiEyeOff } from '@mdi/js'; |
2 | import Icon from '@mdi/react'; | ||
3 | import classnames from 'classnames'; | 2 | import classnames from 'classnames'; |
4 | import { | 3 | import { |
5 | Component, | 4 | Component, |
@@ -7,9 +6,13 @@ import { | |||
7 | InputHTMLAttributes, | 6 | InputHTMLAttributes, |
8 | ReactElement, | 7 | ReactElement, |
9 | RefObject, | 8 | RefObject, |
9 | KeyboardEvent, | ||
10 | } from 'react'; | 10 | } from 'react'; |
11 | import injectSheet, { WithStylesProps } from 'react-jss'; | 11 | import withStyles, { WithStylesProps } from 'react-jss'; |
12 | import { noop } from 'lodash'; | 12 | import { noop } from 'lodash'; |
13 | import { observer } from 'mobx-react'; | ||
14 | import { defineMessages, injectIntl, WrappedComponentProps } from 'react-intl'; | ||
15 | import Icon from '../icon'; | ||
13 | import { IFormField } from '../typings/generic'; | 16 | import { IFormField } from '../typings/generic'; |
14 | import Error from '../error'; | 17 | import Error from '../error'; |
15 | import Label from '../label'; | 18 | import Label from '../label'; |
@@ -17,6 +20,12 @@ import Wrapper from '../wrapper'; | |||
17 | import { scorePasswordFunc } from './scorePassword'; | 20 | import { scorePasswordFunc } from './scorePassword'; |
18 | import styles from './styles'; | 21 | import styles from './styles'; |
19 | 22 | ||
23 | const messages = defineMessages({ | ||
24 | passwordToggle: { | ||
25 | id: 'settings.app.form.passwordToggle', | ||
26 | defaultMessage: 'Password toggle', | ||
27 | }, | ||
28 | }); | ||
20 | interface IData { | 29 | interface IData { |
21 | [index: string]: string; | 30 | [index: string]: string; |
22 | } | 31 | } |
@@ -24,7 +33,8 @@ interface IData { | |||
24 | interface IProps | 33 | interface IProps |
25 | extends InputHTMLAttributes<HTMLInputElement>, | 34 | extends InputHTMLAttributes<HTMLInputElement>, |
26 | IFormField, | 35 | IFormField, |
27 | WithStylesProps<typeof styles> { | 36 | WithStylesProps<typeof styles>, |
37 | WrappedComponentProps { | ||
28 | focus?: boolean; | 38 | focus?: boolean; |
29 | prefix?: string; | 39 | prefix?: string; |
30 | suffix?: string; | 40 | suffix?: string; |
@@ -40,8 +50,10 @@ interface IState { | |||
40 | passwordScore: number; | 50 | passwordScore: number; |
41 | } | 51 | } |
42 | 52 | ||
43 | class InputComponent extends Component<IProps, IState> { | 53 | @observer |
44 | private inputRef: RefObject<HTMLInputElement> = createRef<HTMLInputElement>(); | 54 | class Input extends Component<IProps, IState> { |
55 | private inputElement: RefObject<HTMLInputElement> = | ||
56 | createRef<HTMLInputElement>(); | ||
45 | 57 | ||
46 | constructor(props: IProps) { | 58 | constructor(props: IProps) { |
47 | super(props); | 59 | super(props); |
@@ -53,36 +65,40 @@ class InputComponent extends Component<IProps, IState> { | |||
53 | } | 65 | } |
54 | 66 | ||
55 | componentDidMount(): void { | 67 | componentDidMount(): void { |
56 | const { focus, data = {} } = this.props; | 68 | const { focus = false, data = {} } = this.props; |
57 | 69 | ||
58 | if (this.inputRef && this.inputRef.current) { | 70 | if (this.inputElement && this.inputElement.current) { |
59 | if (focus) { | 71 | if (focus) { |
60 | this.inputRef.current.focus(); | 72 | this.inputElement.current.focus(); |
61 | } | 73 | } |
62 | 74 | ||
63 | for (const key of Object.keys(data)) | 75 | for (const key of Object.keys(data)) { |
64 | this.inputRef.current!.dataset[key] = data[key]; | 76 | this.inputElement.current.dataset[key] = data[key]; |
77 | } | ||
65 | } | 78 | } |
66 | } | 79 | } |
67 | 80 | ||
68 | onChange(e: React.ChangeEvent<HTMLInputElement>): void { | 81 | onChange(e: React.ChangeEvent<HTMLInputElement>): void { |
69 | const { scorePassword, onChange } = this.props; | 82 | const { scorePassword, onChange = noop } = this.props; |
70 | 83 | ||
71 | if (onChange) { | 84 | onChange(e); |
72 | onChange(e); | ||
73 | } | ||
74 | 85 | ||
75 | if (this.inputRef && this.inputRef.current && scorePassword) { | 86 | if (scorePassword) { |
87 | console.log( | ||
88 | '--->', | ||
89 | e.target.value, | ||
90 | scorePasswordFunc(e.target.value as string), | ||
91 | ); | ||
76 | this.setState({ | 92 | this.setState({ |
77 | passwordScore: scorePasswordFunc(this.inputRef.current.value), | 93 | passwordScore: scorePasswordFunc(e.target.value), |
78 | }); | 94 | }); |
79 | } | 95 | } |
80 | } | 96 | } |
81 | 97 | ||
82 | onInputKeyPress(e: React.KeyboardEvent): void { | 98 | onInputKeyPress(e: KeyboardEvent<HTMLInputElement>): void { |
83 | if (e.key === 'Enter') { | 99 | if (e.key === 'Enter') { |
84 | const { onEnterKey } = this.props; | 100 | const { onEnterKey = noop } = this.props; |
85 | onEnterKey && onEnterKey(); | 101 | onEnterKey(); |
86 | } | 102 | } |
87 | } | 103 | } |
88 | 104 | ||
@@ -113,10 +129,9 @@ class InputComponent extends Component<IProps, IState> { | |||
113 | type = 'text', | 129 | type = 'text', |
114 | disabled = false, | 130 | disabled = false, |
115 | readOnly, | 131 | readOnly, |
132 | intl, | ||
116 | } = this.props; | 133 | } = this.props; |
117 | |||
118 | const { showPassword, passwordScore } = this.state; | 134 | const { showPassword, passwordScore } = this.state; |
119 | |||
120 | const inputType = type === 'password' && showPassword ? 'text' : type; | 135 | const inputType = type === 'password' && showPassword ? 'text' : type; |
121 | 136 | ||
122 | return ( | 137 | return ( |
@@ -125,79 +140,85 @@ class InputComponent extends Component<IProps, IState> { | |||
125 | identifier="franz-input" | 140 | identifier="franz-input" |
126 | noMargin={noMargin} | 141 | noMargin={noMargin} |
127 | > | 142 | > |
128 | <Label | 143 | {label && showLabel && ( |
129 | title={label} | 144 | <Label |
130 | showLabel={showLabel} | 145 | title={label} |
131 | htmlFor={id} | 146 | showLabel={showLabel} |
132 | className={classes.label} | 147 | htmlFor={id} |
133 | isRequired={required} | 148 | className={classes.label} |
149 | isRequired={required} | ||
150 | /> | ||
151 | )} | ||
152 | <div | ||
153 | className={classnames({ | ||
154 | [`${inputClassName}`]: inputClassName, | ||
155 | // [`${classes.hasPasswordScore}`]: scorePassword, | ||
156 | [`${classes.wrapper}`]: true, | ||
157 | [`${classes.disabled}`]: disabled, | ||
158 | [`${classes.hasError}`]: error, | ||
159 | })} | ||
134 | > | 160 | > |
161 | {prefix && <span className={classes.prefix}>{prefix}</span>} | ||
162 | <input | ||
163 | id={id} | ||
164 | type={inputType} | ||
165 | name={name} | ||
166 | value={value as string} | ||
167 | placeholder={placeholder} | ||
168 | spellCheck={spellCheck} | ||
169 | className={classes.input} | ||
170 | ref={this.inputElement} | ||
171 | onChange={this.onChange.bind(this)} | ||
172 | onFocus={onFocus} | ||
173 | onBlur={onBlur} | ||
174 | disabled={disabled} | ||
175 | onKeyPress={this.onInputKeyPress.bind(this)} | ||
176 | min={min} | ||
177 | max={max} | ||
178 | step={step} | ||
179 | readOnly={readOnly} | ||
180 | /> | ||
181 | |||
182 | {suffix && <span className={classes.suffix}>{suffix}</span>} | ||
183 | |||
184 | {showPasswordToggle && ( | ||
185 | <button | ||
186 | type="button" | ||
187 | className={classnames({ | ||
188 | 'franz-form__input-modifier': true, | ||
189 | })} | ||
190 | onClick={() => | ||
191 | this.setState(prevState => ({ | ||
192 | showPassword: !prevState.showPassword, | ||
193 | })) | ||
194 | } | ||
195 | tabIndex={-1} | ||
196 | aria-label={intl.formatMessage(messages.passwordToggle)} | ||
197 | > | ||
198 | <Icon icon={this.state.showPassword ? mdiEye : mdiEyeOff} /> | ||
199 | </button> | ||
200 | )} | ||
201 | </div> | ||
202 | {scorePassword && ( | ||
135 | <div | 203 | <div |
136 | className={classnames({ | 204 | className={classnames({ |
137 | [`${inputClassName}`]: inputClassName, | 205 | [`${classes.passwordScore}`]: true, |
138 | [`${classes.hasPasswordScore}`]: scorePassword, | ||
139 | [`${classes.wrapper}`]: true, | ||
140 | [`${classes.disabled}`]: disabled, | ||
141 | [`${classes.hasError}`]: error, | 206 | [`${classes.hasError}`]: error, |
142 | })} | 207 | })} |
143 | > | 208 | > |
144 | {prefix && <span className={classes.prefix}>{prefix}</span>} | 209 | <meter |
145 | <input | 210 | value={passwordScore < 5 ? 5 : passwordScore} |
146 | id={id} | 211 | low={30} |
147 | type={inputType} | 212 | high={75} |
148 | name={name} | 213 | optimum={100} |
149 | value={value as string} | 214 | max={100} |
150 | placeholder={placeholder} | ||
151 | spellCheck={spellCheck} | ||
152 | className={classes.input} | ||
153 | ref={this.inputRef} | ||
154 | onChange={this.onChange.bind(this)} | ||
155 | onFocus={onFocus} | ||
156 | onBlur={onBlur} | ||
157 | disabled={disabled} | ||
158 | onKeyPress={this.onInputKeyPress.bind(this)} | ||
159 | min={min} | ||
160 | max={max} | ||
161 | step={step} | ||
162 | readOnly={readOnly} | ||
163 | /> | 215 | /> |
164 | {suffix && <span className={classes.suffix}>{suffix}</span>} | ||
165 | {showPasswordToggle && ( | ||
166 | <button | ||
167 | type="button" | ||
168 | className={classes.formModifier} | ||
169 | onClick={() => | ||
170 | this.setState(prevState => ({ | ||
171 | showPassword: !prevState.showPassword, | ||
172 | })) | ||
173 | } | ||
174 | tabIndex={-1} | ||
175 | > | ||
176 | <Icon path={!showPassword ? mdiEye : mdiEyeOff} /> | ||
177 | </button> | ||
178 | )} | ||
179 | </div> | 216 | </div> |
180 | {scorePassword && ( | 217 | )} |
181 | <div | ||
182 | className={classnames({ | ||
183 | [`${classes.passwordScore}`]: true, | ||
184 | [`${classes.hasError}`]: error, | ||
185 | })} | ||
186 | > | ||
187 | <meter | ||
188 | value={passwordScore < 5 ? 5 : passwordScore} | ||
189 | low={30} | ||
190 | high={75} | ||
191 | optimum={100} | ||
192 | max={100} | ||
193 | /> | ||
194 | </div> | ||
195 | )} | ||
196 | </Label> | ||
197 | {error && <Error message={error} />} | 218 | {error && <Error message={error} />} |
198 | </Wrapper> | 219 | </Wrapper> |
199 | ); | 220 | ); |
200 | } | 221 | } |
201 | } | 222 | } |
202 | 223 | ||
203 | export default injectSheet(styles, { injectTheme: true })(InputComponent); | 224 | export default injectIntl(withStyles(styles, { injectTheme: true })(Input)); |
diff --git a/src/components/ui/input/styles.ts b/src/components/ui/input/styles.ts index 04c1b3991..ebae0e40d 100644 --- a/src/components/ui/input/styles.ts +++ b/src/components/ui/input/styles.ts | |||
@@ -49,7 +49,6 @@ export default (theme: Theme) => ({ | |||
49 | }, | 49 | }, |
50 | passwordScore: { | 50 | passwordScore: { |
51 | background: theme.inputScorePasswordBackground, | 51 | background: theme.inputScorePasswordBackground, |
52 | border: theme.inputBorder, | ||
53 | borderTopWidth: 0, | 52 | borderTopWidth: 0, |
54 | borderBottomLeftRadius: theme.borderRadiusSmall, | 53 | borderBottomLeftRadius: theme.borderRadiusSmall, |
55 | borderBottomRightRadius: theme.borderRadiusSmall, | 54 | borderBottomRightRadius: theme.borderRadiusSmall, |