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

[PROD] Next Release #83

Merged
merged 10 commits into from
Aug 9, 2021
Merged
2 changes: 2 additions & 0 deletions src/assets/images/icon-menu-item-roles.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 22 additions & 0 deletions src/assets/images/icon-role-fallback.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 18 additions & 0 deletions src/assets/images/icon-search.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
29 changes: 29 additions & 0 deletions src/components/Icons/Roles/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from "react";
import PT from "prop-types";
import cn from "classnames";
import IconWrapper from "components/IconWrapper";
import IconRoleManagement from "../../../assets/images/icon-menu-item-roles.svg";
import styles from "./styles.module.scss";

/**
* Displays a "role management" icon used in navigation menu.
*
* @param {Object} props component props
* @param {string} [props.className] class name added to root element
* @param {boolean} [props.isActive] a flag indicating whether the icon is active
* @returns {JSX.Element}
*/
const Roles = ({ className, isActive = false }) => (
<IconWrapper
className={cn(styles.container, className, { [styles.isActive]: isActive })}
>
<IconRoleManagement />
</IconWrapper>
);

Roles.propTypes = {
className: PT.string,
isActive: PT.bool,
};

export default Roles;
19 changes: 19 additions & 0 deletions src/components/Icons/Roles/styles.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.container {
svg {
display: block;
width: auto;
height: 100%;

path {
fill: #7f7f7f;
}
}

&.isActive {
svg {
path {
fill: #06d6a0;
}
}
}
}
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;
}
6 changes: 6 additions & 0 deletions src/components/Sidebar/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import NavMenu from "components/NavMenu";
import styles from "./styles.module.scss";
import WorkPeriods from "components/Icons/WorkPeriods";
import Freelancers from "components/Icons/Freelancers";
import Roles from "components/Icons/Roles";
import { APP_BASE_PATH } from "../../constants";

/**
Expand Down Expand Up @@ -38,6 +39,11 @@ const NAV_ITEMS = [
label: "Freelancers",
path: `${APP_BASE_PATH}/freelancers`,
},
{
icon: Roles,
label: "Roles",
path: `${APP_BASE_PATH}/roles`,
},
];

export default Sidebar;
Loading