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

Roles page updates #87

Merged
merged 2 commits into from
Aug 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 95 additions & 32 deletions src/components/IntegerField/index.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React from "react";
import React, { useMemo } from "react";
import PT from "prop-types";
import cn from "classnames";
import IconExclamationMark from "components/Icons/ExclamationMarkCircled";
import Popover from "components/Popover";
import styles from "./styles.module.scss";

/**
Expand All @@ -9,58 +11,119 @@ import styles from "./styles.module.scss";
* @param {Object} props component properties
* @param {string} [props.className] class name to be added to root element
* @param {boolean} [props.isDisabled] if the field is disabled
* @param {boolean} [props.readOnly] if the field is readOnly
* @param {boolean} [props.displayButtons] whether to display +/- buttons
* @param {string} props.name field's name
* @param {number} props.value field's value
* @param {number} [props.maxValue] maximum allowed value
* @param {number} [props.minValue] minimum allowed value
* @param {(v: number) => void} props.onChange
* @param {(v: number) => void} [props.onChange]
* @param {(v: string) => void} [props.onInputChange]
* @returns {JSX.Element}
*/
const IntegerField = ({
className,
isDisabled = false,
readOnly = true,
displayButtons = true,
name,
onInputChange,
onChange,
value,
maxValue = Infinity,
minValue = -Infinity,
}) => (
<div className={cn(styles.container, className)}>
<input
disabled={isDisabled}
readOnly
className={styles.input}
name={name}
value={value}
/>
<button
className={styles.btnMinus}
onClick={(event) => {
event.stopPropagation();
if (!isDisabled) {
onChange(Math.max(value - 1, minValue));
}
}}
/>
<button
className={styles.btnPlus}
onClick={(event) => {
event.stopPropagation();
if (!isDisabled) {
onChange(Math.min(+value + 1, maxValue));
}
}}
/>
</div>
);
}) => {
const isInvalid = useMemo(
() =>
!!value &&
(isNaN(value) ||
!Number.isInteger(+value) ||
+value > maxValue ||
+value < minValue),
[value, minValue, maxValue]
);

const errorPopupContent = useMemo(() => {
if (value && (isNaN(value) || !Number.isInteger(+value))) {
return <>You must enter a valid integer.</>;
}
if (+value > maxValue) {
return (
<>
You must enter an integer less than or equal to{" "}
<strong>{maxValue}</strong>.
</>
);
}
if (+value < minValue) {
return (
<>
You must enter an integer greater than or equal to{" "}
<strong>{minValue}</strong>.
</>
);
}
}, [value, minValue, maxValue]);

return (
<div className={cn(styles.container, className)}>
{isInvalid && (
<Popover
className={styles.popup}
stopClickPropagation={true}
content={errorPopupContent}
strategy="fixed"
>
<IconExclamationMark className={styles.icon} />
</Popover>
)}
<input
type="number"
onChange={(event) => onInputChange && onInputChange(event.target.value)}
disabled={isDisabled}
readOnly={readOnly}
className={cn(styles.input, {
error: isInvalid,
})}
name={name}
value={value}
/>
{displayButtons && (
<>
<button
className={styles.btnMinus}
onClick={(event) => {
event.stopPropagation();
if (!isDisabled) {
onChange(Math.max(value - 1, minValue));
}
}}
/>
<button
className={styles.btnPlus}
onClick={(event) => {
event.stopPropagation();
if (!isDisabled) {
onChange(Math.min(+value + 1, maxValue));
}
}}
/>
</>
)}
</div>
);
};

IntegerField.propTypes = {
className: PT.string,
isDisabled: PT.bool,
readOnly: PT.bool,
displayButtons: PT.bool,
name: PT.string.isRequired,
maxValue: PT.number,
minValue: PT.number,
onChange: PT.func.isRequired,
onChange: PT.func,
onInputChange: PT.func,
value: PT.number.isRequired,
};

Expand Down
19 changes: 19 additions & 0 deletions src/components/IntegerField/styles.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ input.input {
outline: none !important;
box-shadow: none !important;
text-align: center;
appearance: textfield;
&::-webkit-outer-spin-button, &::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}

&:disabled {
border-color: $control-disabled-border-color;
Expand Down Expand Up @@ -94,3 +99,17 @@ input.input {
height: 9px;
}
}

.popup {
margin-right: 5px;
max-width: 400px;
max-height: 200px;
line-height: $line-height-px;
white-space: normal;
}

.icon {
padding-top: 1px;
width: 15px;
height: 15px;
}
32 changes: 22 additions & 10 deletions src/components/Typeahead/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ const selectComponents = {
* @param {function} props.onChange function called when value changes
* @param {function} [props.onInputChange] function called when input value changes
* @param {function} [props.onBlur] function called on input blur
* @param {number} [props.minLengthForSuggestions] the minimum string lenth for displaying suggestions (default 3)
* @param {Boolean} [props.enforceListOnlySelection] enforces user to select from the list - manual inputs (if not in the list) won't affect the selection
* @param {string} props.value input value
* @param {function} props.getSuggestions the function to get suggestions
* @param {string} props.targetProp the target property of the returned object from getSuggestions
Expand All @@ -87,6 +89,8 @@ const Typeahead = ({
onChange,
onInputChange,
onBlur,
minLengthForSuggestions = 3,
enforceListOnlySelection = false,
placeholder,
value,
getSuggestions,
Expand Down Expand Up @@ -139,7 +143,12 @@ const Typeahead = ({
setIsMenuFocused(false);
setIsMenuOpen(false);
setIsLoading(false);
onChange(inputValue);
// fire onChange event
// - if `enforceListOnlySelection` is not set,
// - or if it's set and options list contains the value
if (!enforceListOnlySelection || options.includes(inputValue)) {
onChange(inputValue);
}
}
} else if (key === "ArrowDown") {
if (!isMenuFocused) {
Expand All @@ -158,7 +167,12 @@ const Typeahead = ({
const onSelectBlur = () => {
setIsMenuFocused(false);
setIsMenuOpen(false);
onChange(inputValue);
// fire onChange event
// - if `enforceListOnlySelection` is not set,
// - or if it's set and options list contains the value
if (!enforceListOnlySelection || options.includes(inputValue)) {
onChange(inputValue);
}
onBlur && onBlur();
};

Expand All @@ -170,11 +184,10 @@ const Typeahead = ({
}
setIsLoading(true);
setIsMenuOpen(true);
const options = await loadSuggestions(
getSuggestions,
value,
targetProp
);
const options =
value.length < minLengthForSuggestions
? [] // no suggestions yet if value length is less than `minLengthForSuggestions`
: await loadSuggestions(getSuggestions, value, targetProp);
if (!isChangeAppliedRef.current) {
setOptions(options);
setIsLoading(false);
Expand Down Expand Up @@ -233,9 +246,6 @@ const Typeahead = ({

const loadSuggestions = async (getSuggestions, inputValue, targetProp) => {
let options = [];
if (inputValue.length < 3) {
return options;
}
try {
const res = await getSuggestions(inputValue);
const items = res.data.slice(0, 100);
Expand Down Expand Up @@ -266,6 +276,8 @@ Typeahead.propTypes = {
onChange: PT.func.isRequired,
onInputChange: PT.func,
onBlur: PT.func,
minLengthForSuggestions: PT.number,
enforceListOnlySelection: PT.bool,
placeholder: PT.string,
value: PT.oneOfType([PT.number, PT.string]),
getSuggestions: PT.func,
Expand Down
Loading