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