1
1
import {
2
- Alert ,
3
2
ControlLabel ,
4
3
FormControl ,
5
4
FormGroup ,
6
5
HelpBlock
7
6
} from '@freecodecamp/react-bootstrap' ;
8
- import { kebabCase } from 'lodash-es' ;
7
+ import { kebabCase , set } from 'lodash-es' ;
9
8
import normalizeUrl from 'normalize-url' ;
10
- import React from 'react' ;
9
+ import React , { Fragment , useState } from 'react' ;
11
10
import { Field } from 'react-final-form' ;
12
- import { useTranslation } from 'react-i18next ' ;
11
+ import Warning from '../../assets/icons/warning ' ;
13
12
import { FormOptions } from './form' ;
14
13
import {
15
14
editorValidator ,
@@ -18,14 +17,14 @@ import {
18
17
fCCValidator ,
19
18
httpValidator
20
19
} from './form-validators' ;
20
+ import './form-field.css' ;
21
21
22
22
type FormFieldsProps = {
23
23
formFields : { name : string ; label : string } [ ] ;
24
24
options : FormOptions ;
25
25
} ;
26
26
27
27
function FormFields ( props : FormFieldsProps ) : JSX . Element {
28
- const { t } = useTranslation ( ) ;
29
28
const { formFields, options = { } } : FormFieldsProps = props ;
30
29
const {
31
30
ignored = [ ] ,
@@ -36,6 +35,10 @@ function FormFields(props: FormFieldsProps): JSX.Element {
36
35
isLocalLinkAllowed = false
37
36
} = options ;
38
37
38
+ const [ blured , setBlured ] = useState < boolean [ ] > ( [ ] ) ;
39
+ const markAsBlured = ( index : number ) =>
40
+ setBlured ( prevState => set ( [ ...prevState ] , index , true ) ) ;
41
+
39
42
const nullOrWarning = (
40
43
value : string ,
41
44
error : unknown ,
@@ -59,22 +62,29 @@ function FormFields(props: FormFieldsProps): JSX.Element {
59
62
const message : string = ( error ||
60
63
validationError ||
61
64
validationWarning ) as string ;
65
+
66
+ const hasError = error || validationError ;
67
+ const classNames = [
68
+ 'input-message' ,
69
+ hasError && 'is-error' ,
70
+ ! hasError && 'is-warn'
71
+ ]
72
+ . filter ( Boolean )
73
+ . join ( ' ' ) ;
62
74
return message ? (
63
- < HelpBlock >
64
- < Alert
65
- bsStyle = { error || validationError ? 'danger' : 'info' }
66
- closeLabel = { t ( 'buttons.close' ) }
67
- >
75
+ < HelpBlock className = 'input-help-box' >
76
+ < div className = { classNames } >
77
+ < Warning />
68
78
{ message }
69
- </ Alert >
79
+ </ div >
70
80
</ HelpBlock >
71
81
) : null ;
72
82
} ;
73
83
return (
74
84
< >
75
85
{ formFields
76
86
. filter ( formField => ! ignored . includes ( formField . name ) )
77
- . map ( ( { name, label } ) => (
87
+ . map ( ( { name, label } , i ) => (
78
88
// TODO: verify if the value is always a string
79
89
< Field key = { `${ kebabCase ( name ) } -field` } name = { name } >
80
90
{ ( { input : { value, onChange } , meta : { pristine, error } } ) => {
@@ -84,33 +94,37 @@ function FormFields(props: FormFieldsProps): JSX.Element {
84
94
name in placeholders ? placeholders [ name ] : '' ;
85
95
const isURL = types [ name ] === 'url' ;
86
96
return (
87
- < FormGroup key = { key } className = 'embedded' >
88
- { type === 'hidden' ? null : (
89
- < ControlLabel htmlFor = { key } >
90
- { label }
91
- { required . includes ( name ) && (
92
- < span className = 'required-star' > *</ span >
93
- ) }
94
- </ ControlLabel >
95
- ) }
96
- < FormControl
97
- componentClass = { type === 'textarea' ? type : 'input' }
98
- id = { key }
99
- name = { name }
100
- onChange = { onChange }
101
- placeholder = { placeholder }
102
- required = { required . includes ( name ) }
103
- rows = { 4 }
104
- type = { type }
105
- value = { value as string }
106
- />
107
- { nullOrWarning (
108
- value as string ,
109
- ! pristine && error ,
110
- isURL ,
111
- name
112
- ) }
113
- </ FormGroup >
97
+ < Fragment key = { key } >
98
+ < FormGroup className = 'embedded' >
99
+ { type === 'hidden' ? null : (
100
+ < ControlLabel htmlFor = { key } >
101
+ { label }
102
+ { required . includes ( name ) && (
103
+ < span className = 'required-star' > *</ span >
104
+ ) }
105
+ </ ControlLabel >
106
+ ) }
107
+ < FormControl
108
+ componentClass = { type === 'textarea' ? type : 'input' }
109
+ id = { key }
110
+ name = { name }
111
+ onChange = { onChange }
112
+ placeholder = { placeholder }
113
+ required = { required . includes ( name ) }
114
+ rows = { 4 }
115
+ type = { type }
116
+ value = { value as string }
117
+ onBlur = { ( ) => markAsBlured ( i ) }
118
+ />
119
+ </ FormGroup >
120
+ { blured [ i ] &&
121
+ nullOrWarning (
122
+ value as string ,
123
+ ! pristine && error ,
124
+ isURL ,
125
+ name
126
+ ) }
127
+ </ Fragment >
114
128
) ;
115
129
} }
116
130
</ Field >
0 commit comments