Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.

Commit 73252ea

Browse files
authored
Merge pull request #87 from cagdas001/dev
Roles page updates
2 parents d99814d + 6104e7d commit 73252ea

File tree

7 files changed

+456
-138
lines changed

7 files changed

+456
-138
lines changed

src/components/IntegerField/index.jsx

+95-32
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import React from "react";
1+
import React, { useMemo } from "react";
22
import PT from "prop-types";
33
import cn from "classnames";
4+
import IconExclamationMark from "components/Icons/ExclamationMarkCircled";
5+
import Popover from "components/Popover";
46
import styles from "./styles.module.scss";
57

68
/**
@@ -9,58 +11,119 @@ import styles from "./styles.module.scss";
911
* @param {Object} props component properties
1012
* @param {string} [props.className] class name to be added to root element
1113
* @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
1216
* @param {string} props.name field's name
1317
* @param {number} props.value field's value
1418
* @param {number} [props.maxValue] maximum allowed value
1519
* @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]
1722
* @returns {JSX.Element}
1823
*/
1924
const IntegerField = ({
2025
className,
2126
isDisabled = false,
27+
readOnly = true,
28+
displayButtons = true,
2229
name,
30+
onInputChange,
2331
onChange,
2432
value,
2533
maxValue = Infinity,
2634
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+
};
56116

57117
IntegerField.propTypes = {
58118
className: PT.string,
59119
isDisabled: PT.bool,
120+
readOnly: PT.bool,
121+
displayButtons: PT.bool,
60122
name: PT.string.isRequired,
61123
maxValue: PT.number,
62124
minValue: PT.number,
63-
onChange: PT.func.isRequired,
125+
onChange: PT.func,
126+
onInputChange: PT.func,
64127
value: PT.number.isRequired,
65128
};
66129

src/components/IntegerField/styles.module.scss

+19
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ input.input {
1919
outline: none !important;
2020
box-shadow: none !important;
2121
text-align: center;
22+
appearance: textfield;
23+
&::-webkit-outer-spin-button, &::-webkit-inner-spin-button {
24+
-webkit-appearance: none;
25+
margin: 0;
26+
}
2227

2328
&:disabled {
2429
border-color: $control-disabled-border-color;
@@ -94,3 +99,17 @@ input.input {
9499
height: 9px;
95100
}
96101
}
102+
103+
.popup {
104+
margin-right: 5px;
105+
max-width: 400px;
106+
max-height: 200px;
107+
line-height: $line-height-px;
108+
white-space: normal;
109+
}
110+
111+
.icon {
112+
padding-top: 1px;
113+
width: 15px;
114+
height: 15px;
115+
}

src/components/Typeahead/index.jsx

+22-10
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ const selectComponents = {
7474
* @param {function} props.onChange function called when value changes
7575
* @param {function} [props.onInputChange] function called when input value changes
7676
* @param {function} [props.onBlur] function called on input blur
77+
* @param {number} [props.minLengthForSuggestions] the minimum string lenth for displaying suggestions (default 3)
78+
* @param {Boolean} [props.enforceListOnlySelection] enforces user to select from the list - manual inputs (if not in the list) won't affect the selection
7779
* @param {string} props.value input value
7880
* @param {function} props.getSuggestions the function to get suggestions
7981
* @param {string} props.targetProp the target property of the returned object from getSuggestions
@@ -87,6 +89,8 @@ const Typeahead = ({
8789
onChange,
8890
onInputChange,
8991
onBlur,
92+
minLengthForSuggestions = 3,
93+
enforceListOnlySelection = false,
9094
placeholder,
9195
value,
9296
getSuggestions,
@@ -139,7 +143,12 @@ const Typeahead = ({
139143
setIsMenuFocused(false);
140144
setIsMenuOpen(false);
141145
setIsLoading(false);
142-
onChange(inputValue);
146+
// fire onChange event
147+
// - if `enforceListOnlySelection` is not set,
148+
// - or if it's set and options list contains the value
149+
if (!enforceListOnlySelection || options.includes(inputValue)) {
150+
onChange(inputValue);
151+
}
143152
}
144153
} else if (key === "ArrowDown") {
145154
if (!isMenuFocused) {
@@ -158,7 +167,12 @@ const Typeahead = ({
158167
const onSelectBlur = () => {
159168
setIsMenuFocused(false);
160169
setIsMenuOpen(false);
161-
onChange(inputValue);
170+
// fire onChange event
171+
// - if `enforceListOnlySelection` is not set,
172+
// - or if it's set and options list contains the value
173+
if (!enforceListOnlySelection || options.includes(inputValue)) {
174+
onChange(inputValue);
175+
}
162176
onBlur && onBlur();
163177
};
164178

@@ -170,11 +184,10 @@ const Typeahead = ({
170184
}
171185
setIsLoading(true);
172186
setIsMenuOpen(true);
173-
const options = await loadSuggestions(
174-
getSuggestions,
175-
value,
176-
targetProp
177-
);
187+
const options =
188+
value.length < minLengthForSuggestions
189+
? [] // no suggestions yet if value length is less than `minLengthForSuggestions`
190+
: await loadSuggestions(getSuggestions, value, targetProp);
178191
if (!isChangeAppliedRef.current) {
179192
setOptions(options);
180193
setIsLoading(false);
@@ -233,9 +246,6 @@ const Typeahead = ({
233246

234247
const loadSuggestions = async (getSuggestions, inputValue, targetProp) => {
235248
let options = [];
236-
if (inputValue.length < 3) {
237-
return options;
238-
}
239249
try {
240250
const res = await getSuggestions(inputValue);
241251
const items = res.data.slice(0, 100);
@@ -266,6 +276,8 @@ Typeahead.propTypes = {
266276
onChange: PT.func.isRequired,
267277
onInputChange: PT.func,
268278
onBlur: PT.func,
279+
minLengthForSuggestions: PT.number,
280+
enforceListOnlySelection: PT.bool,
269281
placeholder: PT.string,
270282
value: PT.oneOfType([PT.number, PT.string]),
271283
getSuggestions: PT.func,

0 commit comments

Comments
 (0)