1
- import React from "react" ;
1
+ import React , { useMemo } from "react" ;
2
2
import PT from "prop-types" ;
3
3
import cn from "classnames" ;
4
+ import IconExclamationMark from "components/Icons/ExclamationMarkCircled" ;
5
+ import Popover from "components/Popover" ;
4
6
import styles from "./styles.module.scss" ;
5
7
6
8
/**
@@ -9,58 +11,119 @@ import styles from "./styles.module.scss";
9
11
* @param {Object } props component properties
10
12
* @param {string } [props.className] class name to be added to root element
11
13
* @param {boolean } [props.isDisabled] if the field is disabled
14
+ * @param {boolean } [props.readOnly] if the field is readOnly
15
+ * @param {boolean } [props.displayButtons] whether to display +/- buttons
12
16
* @param {string } props.name field's name
13
17
* @param {number } props.value field's value
14
18
* @param {number } [props.maxValue] maximum allowed value
15
19
* @param {number } [props.minValue] minimum allowed value
16
- * @param {(v: number) => void } props.onChange
20
+ * @param {(v: number) => void } [props.onChange]
21
+ * @param {(v: string) => void } [props.onInputChange]
17
22
* @returns {JSX.Element }
18
23
*/
19
24
const IntegerField = ( {
20
25
className,
21
26
isDisabled = false ,
27
+ readOnly = true ,
28
+ displayButtons = true ,
22
29
name,
30
+ onInputChange,
23
31
onChange,
24
32
value,
25
33
maxValue = Infinity ,
26
34
minValue = - Infinity ,
27
- } ) => (
28
- < div className = { cn ( styles . container , className ) } >
29
- < input
30
- disabled = { isDisabled }
31
- readOnly
32
- className = { styles . input }
33
- name = { name }
34
- value = { value }
35
- />
36
- < button
37
- className = { styles . btnMinus }
38
- onClick = { ( event ) => {
39
- event . stopPropagation ( ) ;
40
- if ( ! isDisabled ) {
41
- onChange ( Math . max ( value - 1 , minValue ) ) ;
42
- }
43
- } }
44
- />
45
- < button
46
- className = { styles . btnPlus }
47
- onClick = { ( event ) => {
48
- event . stopPropagation ( ) ;
49
- if ( ! isDisabled ) {
50
- onChange ( Math . min ( + value + 1 , maxValue ) ) ;
51
- }
52
- } }
53
- />
54
- </ div >
55
- ) ;
35
+ } ) => {
36
+ const isInvalid = useMemo (
37
+ ( ) =>
38
+ ! ! value &&
39
+ ( isNaN ( value ) ||
40
+ ! Number . isInteger ( + value ) ||
41
+ + value > maxValue ||
42
+ + value < minValue ) ,
43
+ [ value , minValue , maxValue ]
44
+ ) ;
45
+
46
+ const errorPopupContent = useMemo ( ( ) => {
47
+ if ( value && ( isNaN ( value ) || ! Number . isInteger ( + value ) ) ) {
48
+ return < > You must enter a valid integer.</ > ;
49
+ }
50
+ if ( + value > maxValue ) {
51
+ return (
52
+ < >
53
+ You must enter an integer less than or equal to{ " " }
54
+ < strong > { maxValue } </ strong > .
55
+ </ >
56
+ ) ;
57
+ }
58
+ if ( + value < minValue ) {
59
+ return (
60
+ < >
61
+ You must enter an integer greater than or equal to{ " " }
62
+ < strong > { minValue } </ strong > .
63
+ </ >
64
+ ) ;
65
+ }
66
+ } , [ value , minValue , maxValue ] ) ;
67
+
68
+ return (
69
+ < div className = { cn ( styles . container , className ) } >
70
+ { isInvalid && (
71
+ < Popover
72
+ className = { styles . popup }
73
+ stopClickPropagation = { true }
74
+ content = { errorPopupContent }
75
+ strategy = "fixed"
76
+ >
77
+ < IconExclamationMark className = { styles . icon } />
78
+ </ Popover >
79
+ ) }
80
+ < input
81
+ type = "number"
82
+ onChange = { ( event ) => onInputChange && onInputChange ( event . target . value ) }
83
+ disabled = { isDisabled }
84
+ readOnly = { readOnly }
85
+ className = { cn ( styles . input , {
86
+ error : isInvalid ,
87
+ } ) }
88
+ name = { name }
89
+ value = { value }
90
+ />
91
+ { displayButtons && (
92
+ < >
93
+ < button
94
+ className = { styles . btnMinus }
95
+ onClick = { ( event ) => {
96
+ event . stopPropagation ( ) ;
97
+ if ( ! isDisabled ) {
98
+ onChange ( Math . max ( value - 1 , minValue ) ) ;
99
+ }
100
+ } }
101
+ />
102
+ < button
103
+ className = { styles . btnPlus }
104
+ onClick = { ( event ) => {
105
+ event . stopPropagation ( ) ;
106
+ if ( ! isDisabled ) {
107
+ onChange ( Math . min ( + value + 1 , maxValue ) ) ;
108
+ }
109
+ } }
110
+ />
111
+ </ >
112
+ ) }
113
+ </ div >
114
+ ) ;
115
+ } ;
56
116
57
117
IntegerField . propTypes = {
58
118
className : PT . string ,
59
119
isDisabled : PT . bool ,
120
+ readOnly : PT . bool ,
121
+ displayButtons : PT . bool ,
60
122
name : PT . string . isRequired ,
61
123
maxValue : PT . number ,
62
124
minValue : PT . number ,
63
- onChange : PT . func . isRequired ,
125
+ onChange : PT . func ,
126
+ onInputChange : PT . func ,
64
127
value : PT . number . isRequired ,
65
128
} ;
66
129
0 commit comments