{
Select all}
+ label="Select all"
data-testid="test-group-select-all"
checked={selectableProps.isAllSelected}
onChange={({ target }) => {
@@ -45,7 +45,7 @@ storiesOf("Form Components| FormCheckboxGroup", module).add("default", () => {
/>
Deselect all}
+ label="Deselect all"
data-testid="test-group-deselect-all"
checked={selectableProps.isAllDeselected}
onChange={({ target }) => {
diff --git a/src/components/form/components/FormCheckboxGroup/FormCheckboxGroupItem/FormCheckboxGroupItemTitle.jsx b/src/components/form/components/FormCheckboxGroup/FormCheckboxGroupItem/FormCheckboxGroupItemTitle.jsx
new file mode 100644
index 00000000..8905d44f
--- /dev/null
+++ b/src/components/form/components/FormCheckboxGroup/FormCheckboxGroupItem/FormCheckboxGroupItemTitle.jsx
@@ -0,0 +1,20 @@
+import styled, { css } from "styled-components";
+
+import { FormLabelTitle } from "../../FormLabel";
+
+function getStyles({ checked }) {
+ return checked
+ ? css`
+ font-weight: bold;
+ `
+ : null;
+}
+
+const FormCheckboxGroupItemTitle = styled(FormLabelTitle)`
+ font-size: 16px;
+ line-height: 26px;
+
+ ${getStyles}
+`;
+
+export { FormCheckboxGroupItemTitle };
diff --git a/src/components/form/components/FormCheckboxGroup/FormCheckboxGroupItem/StyledFormCheckboxGroupItem.jsx b/src/components/form/components/FormCheckboxGroup/FormCheckboxGroupItem/StyledFormCheckboxGroupItem.jsx
index 3fd3925e..4dfa95d6 100644
--- a/src/components/form/components/FormCheckboxGroup/FormCheckboxGroupItem/StyledFormCheckboxGroupItem.jsx
+++ b/src/components/form/components/FormCheckboxGroup/FormCheckboxGroupItem/StyledFormCheckboxGroupItem.jsx
@@ -1,8 +1,5 @@
import styled from "styled-components";
export const StyledFormCheckboxGroupItem = styled.div`
- &:not(:last-of-type) {
- margin-bottom: 10px;
- line-height: 1.4;
- }
+ height: 26px;
`;
diff --git a/src/components/form/components/FormCheckboxGroup/FormCheckboxGroupItem/index.jsx b/src/components/form/components/FormCheckboxGroup/FormCheckboxGroupItem/index.jsx
index a5acfac5..b6f2b948 100644
--- a/src/components/form/components/FormCheckboxGroup/FormCheckboxGroupItem/index.jsx
+++ b/src/components/form/components/FormCheckboxGroup/FormCheckboxGroupItem/index.jsx
@@ -2,9 +2,10 @@ import React from "react";
import PropTypes from "prop-types";
import { FormCheckbox } from "../../FormCheckbox";
-import { FormLabel, FormLabelTitle } from "../../FormLabel";
+import { FormLabel } from "../../FormLabel";
import { StyledFormCheckboxGroupItem } from "./StyledFormCheckboxGroupItem";
+import { FormCheckboxGroupItemTitle } from "./FormCheckboxGroupItemTitle";
function FormCheckboxGroupItem({
label,
@@ -18,7 +19,9 @@ function FormCheckboxGroupItem({
return (
- {label}
+
+ {label}
+
);
}
-describe("FormCheckbox tests", () => {
- test("FormCheckbox should change checked state by click", () => {
+describe("FormCheckboxGroup tests", () => {
+ test("FormCheckboxGroup should change checked state by click", () => {
const { getByTestId } = renderFormCheckboxGroup();
expect(getByTestId("test-form")).toHaveFormValues({
diff --git a/src/components/form/components/FormCheckboxGroup/index.jsx b/src/components/form/components/FormCheckboxGroup/index.jsx
index e7b8f30b..83c28219 100644
--- a/src/components/form/components/FormCheckboxGroup/index.jsx
+++ b/src/components/form/components/FormCheckboxGroup/index.jsx
@@ -1,38 +1,40 @@
import React from "react";
import PropTypes from "prop-types";
+import { StyledFormCheckboxGroup } from "./StyledFormCheckboxGroup";
import { FormCheckboxGroupItem } from "./FormCheckboxGroupItem";
-
-import { SelectableList } from "../../../SelectableList";
+import { useSelectableList } from "../../../../hooks/index";
function FormCheckboxGroup({ value, onChange, groupName, render, options }) {
+ const { ...selectedListOptions } = useSelectableList({
+ options,
+ value,
+ onChange,
+ });
+
return (
- {
- return render({
- ...selectedListProps,
- checkboxes: () => {
- const { selected, handleCheckboxChange } = selectedListProps;
-
- return options.map(({ label, value: optionValue }) => {
- return (
-
- );
- });
- },
- });
- }}
- />
+
+ {render({
+ ...selectedListOptions,
+ checkboxes: () => {
+ const { selected, onCheckboxChange } = selectedListOptions;
+ return options.map(({ label, value: optionValue }) => {
+ const strOption = String(optionValue);
+
+ return (
+
+ );
+ });
+ },
+ })}
+
);
}
diff --git a/src/components/form/components/FormDropdown/FormDropdown.stories.js b/src/components/form/components/FormDropdown/FormDropdown.stories.js
index 70c37880..15384610 100644
--- a/src/components/form/components/FormDropdown/FormDropdown.stories.js
+++ b/src/components/form/components/FormDropdown/FormDropdown.stories.js
@@ -205,7 +205,7 @@ storiesOf("Form Components/FormDropdown", module)
width={number("width", 200)}
withSearch={boolean("with search", false)}
disabled={boolean("disabled", false)}
- renderItem={item => (
+ renderItem={(item) => (
{`${isEven(item.value) ? "π" : "π"} ${item.label}`}
)}
/>
diff --git a/src/components/form/components/FormDropdown/FormDropdownControl.jsx b/src/components/form/components/FormDropdown/FormDropdownControl.jsx
index e8dcf97b..a77a76bf 100644
--- a/src/components/form/components/FormDropdown/FormDropdownControl.jsx
+++ b/src/components/form/components/FormDropdown/FormDropdownControl.jsx
@@ -1,13 +1,11 @@
-import styled, { css } from "styled-components";
+import styled from "styled-components";
import { Button } from "../../../Button";
-import { colors } from "../../../../themes/colors";
+import { colors } from "../../../../style";
import { textTrimStyles } from "../../../Text/TextTrim";
-const FormDropdownControl = styled(Button).attrs(() => ({
- buttonTheme: "reset",
-}))`
+const FormDropdownControl = styled(Button)`
${textTrimStyles}
width: 100%;
position: relative;
diff --git a/src/components/form/components/FormDropdown/FormDropdownMenu.jsx b/src/components/form/components/FormDropdown/FormDropdownMenu.jsx
index 29732767..5ab987ea 100644
--- a/src/components/form/components/FormDropdown/FormDropdownMenu.jsx
+++ b/src/components/form/components/FormDropdown/FormDropdownMenu.jsx
@@ -1,6 +1,6 @@
import styled, { css } from "styled-components";
-import { colors } from "../../../../themes/colors";
+import { colors } from "../../../../style";
const FormDropdownMenu = styled.ul`
box-sizing: border-box;
diff --git a/src/components/form/components/FormDropdown/FormDropdownOption.jsx b/src/components/form/components/FormDropdown/FormDropdownOption.jsx
index 28d52b35..ee3f5770 100644
--- a/src/components/form/components/FormDropdown/FormDropdownOption.jsx
+++ b/src/components/form/components/FormDropdown/FormDropdownOption.jsx
@@ -2,7 +2,32 @@ import styled, { css } from "styled-components";
import { textTrimStyles } from "../../../Text/TextTrim";
-import { colors } from "../../../../themes/colors";
+import { colors } from "../../../../style";
+
+function getHighlightedStyles({ highlighted }) {
+ return highlighted
+ ? css`
+ background-color: #f5f5f5;
+ `
+ : css`
+ &:hover {
+ background-color: ${colors.whiteSimple};
+ }
+ `;
+}
+
+function getDisabledStyles({ disabled }) {
+ return disabled
+ ? css`
+ border-color: #546e7a;
+ opacity: 0.5;
+ `
+ : css`
+ &:hover {
+ background-color: #f5f5f5;
+ }
+ `;
+}
const FormDropdownOption = styled.li`
${textTrimStyles}
@@ -11,39 +36,17 @@ const FormDropdownOption = styled.li`
font-size: 16px;
line-height: 1.63;
transition: background-color 120ms ease-in-out;
- padding: 12px 22px 12px 36px;
+ padding: 12px 36px;
+ ${getHighlightedStyles}
+ ${getDisabledStyles}
&:first-child {
padding-top: 16px;
}
+
&:last-child {
padding-bottom: 16px;
}
-
- ${({ highlighted }) => {
- return highlighted
- ? css`
- background-color: #f5f5f5;
- `
- : css`
- &:hover {
- background-color: ${colors.whiteSimple};
- }
- `;
- }};
-
- ${({ disabled }) => {
- return disabled
- ? css`
- border-color: #546e7a;
- opacity: 0.5;
- `
- : css`
- &:hover {
- background-color: #f5f5f5;
- }
- `;
- }}
`;
export { FormDropdownOption };
diff --git a/src/components/form/components/FormDropdown/FormDropdownOptionSelectedIcon.jsx b/src/components/form/components/FormDropdown/FormDropdownOptionSelectedIcon.jsx
index a85679a3..80e595de 100644
--- a/src/components/form/components/FormDropdown/FormDropdownOptionSelectedIcon.jsx
+++ b/src/components/form/components/FormDropdown/FormDropdownOptionSelectedIcon.jsx
@@ -1,6 +1,6 @@
import styled from "styled-components";
-import { Check } from "../../../../assets/icons";
+import { Check } from "../../../icons";
const FormDropdownOptionSelectedIcon = styled(Check)`
position: absolute;
diff --git a/src/components/form/components/FormDropdown/FormDropdownResetButton.jsx b/src/components/form/components/FormDropdown/FormDropdownResetButton.jsx
index acb5c98e..88522ec4 100644
--- a/src/components/form/components/FormDropdown/FormDropdownResetButton.jsx
+++ b/src/components/form/components/FormDropdown/FormDropdownResetButton.jsx
@@ -1,9 +1,8 @@
import styled from "styled-components";
-import Button from "../../../Button";
-const FormDropdownResetButton = styled(Button).attrs(() => ({
- buttonTheme: "reset",
-}))`
+import { Button } from "../../../Button";
+
+const FormDropdownResetButton = styled(Button)`
position: absolute;
top: 0;
bottom: 0;
diff --git a/src/components/form/components/FormDropdown/StyledFormDropdown.jsx b/src/components/form/components/FormDropdown/StyledFormDropdown.jsx
index 977e9f1a..2ddd8af5 100644
--- a/src/components/form/components/FormDropdown/StyledFormDropdown.jsx
+++ b/src/components/form/components/FormDropdown/StyledFormDropdown.jsx
@@ -1,9 +1,15 @@
-import styled from "styled-components";
+import styled, { css } from "styled-components";
const StyledFormDropdown = styled.div`
position: relative;
width: ${({ width }) => (width ? `${width}px` : "100%")};
opacity: ${({ disabled }) => (disabled ? 0.5 : 1)};
+
+ ${({ isFetching }) =>
+ isFetching &&
+ css`
+ opacity: 0.8;
+ `}
`;
export { StyledFormDropdown };
diff --git a/src/components/form/components/FormDropdown/__tests__/FormDropdown.js b/src/components/form/components/FormDropdown/__tests__/FormDropdown.js
index e91a5ddf..ef34d7b9 100644
--- a/src/components/form/components/FormDropdown/__tests__/FormDropdown.js
+++ b/src/components/form/components/FormDropdown/__tests__/FormDropdown.js
@@ -42,7 +42,7 @@ function renderFormDropdown(props = {}) {
name={componentName}
options={options}
value={value}
- onChange={ev => {
+ onChange={(ev) => {
setValue(ev);
onChangeMock(ev);
}}
diff --git a/src/components/form/components/FormDropdown/index.jsx b/src/components/form/components/FormDropdown/index.jsx
index f413460e..89a89427 100644
--- a/src/components/form/components/FormDropdown/index.jsx
+++ b/src/components/form/components/FormDropdown/index.jsx
@@ -13,7 +13,7 @@ import { FormDropdownInputWrapper } from "./FormDropdownInputWrapper";
import { FormDropdownInput } from "./FormDropdownInput";
import { FormDropdownResetButton } from "./FormDropdownResetButton";
import { FormDropdownOptionSelectedIcon } from "./FormDropdownOptionSelectedIcon";
-import { Times } from "../../../../assets/icons";
+import { Times } from "../../../icons";
import { searchInList, capitalize } from "../../../../utils/helpers";
import { identity, property as prop, isEqual, get } from "lodash-es";
@@ -45,6 +45,7 @@ function FormDropdown({
"data-testid": testId,
multiple,
renderSelected,
+ isFetching,
}) {
if (name) {
testId = name;
@@ -80,7 +81,7 @@ function FormDropdown({
function getHighlighted(selected) {
const selectedOptionIndex = selected
? searchInList(options, inputValue, ["label"]).findIndex(
- item => item.value === selected.value
+ (item) => item.value === selected.value
)
: 0;
@@ -197,13 +198,13 @@ function FormDropdown({
if (multiple) {
const selectedInOptions = selected.find(
- item => item.value === option.value
+ (item) => item.value === option.value
);
if (selectedInOptions) {
const changes = selected
- .filter(option => !isSingle(option))
- .filter(option => !isEqual(option, selectedInOptions));
+ .filter((option) => !isSingle(option))
+ .filter((option) => !isEqual(option, selectedInOptions));
if (hasDefault && changes.length === 0) {
onChange(defaultOptions);
@@ -211,7 +212,7 @@ function FormDropdown({
onChange(changes);
}
} else {
- onChange(selected.filter(option => !isSingle(option)).concat(option));
+ onChange(selected.filter((option) => !isSingle(option)).concat(option));
}
} else {
onChange(option);
@@ -243,7 +244,7 @@ function FormDropdown({
function getIsOptionSelected(option, selectedItem) {
if (multiple) {
return (
- selectedItem.find(item => item.value === option.value) !== undefined
+ selectedItem.find((item) => item.value === option.value) !== undefined
);
} else {
return isEqual(selectedItem, option);
@@ -255,7 +256,7 @@ function FormDropdown({
initialSelectedItem={value}
selectedItem={selected}
inputValue={inputValue}
- itemToString={item => (item ? item.label : "")}
+ itemToString={(item) => (item ? item.label : "")}
stateReducer={stateReducer}
onChange={handleChange}
onStateChange={handleStateChange}
@@ -283,6 +284,7 @@ function FormDropdown({
disabled,
className,
"data-testid": testId,
+ isFetching,
})}
>
{
+ onClick={(e) => {
e.stopPropagation();
clearSelection();
closeMenu();
@@ -311,10 +313,9 @@ function FormDropdown({
{isOpen && (
@@ -323,8 +324,8 @@ function FormDropdown({
)}
@@ -346,7 +347,7 @@ function FormDropdown({
"data-testid": `${testId}-option-${item.value}`,
})}
>
- {selected && (
+ {multiple && selected && (
)}
{renderItem(item)}
@@ -366,12 +367,19 @@ function FormDropdown({
const optionShape = PropTypes.shape({
label: PropTypes.string.isRequired,
- value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
+ value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
});
FormDropdown.propTypes = {
name: PropTypes.string,
- value: PropTypes.oneOfType([PropTypes.arrayOf(optionShape), optionShape]),
+ value: PropTypes.oneOfType([
+ optionShape,
+ PropTypes.arrayOf(optionShape),
+ PropTypes.string,
+ PropTypes.arrayOf(PropTypes.string),
+ PropTypes.number,
+ PropTypes.arrayOf(PropTypes.number),
+ ]),
withSearch: PropTypes.bool,
multiple: PropTypes.bool,
disabled: PropTypes.bool,
diff --git a/src/components/form/components/FormField/FormFieldIcon.jsx b/src/components/form/components/FormField/FormFieldIcon.jsx
index 8516f697..ac690770 100644
--- a/src/components/form/components/FormField/FormFieldIcon.jsx
+++ b/src/components/form/components/FormField/FormFieldIcon.jsx
@@ -1,9 +1,13 @@
import styled from "styled-components";
-const FormFieldIcon = styled.span`
- display: inline-block;
- margin-left: 5px;
- color: #aeb9be;
+import { InfoCircle } from "../../../icons";
+
+import { colors } from "../../../../style";
+
+const FormFieldIcon = styled(InfoCircle)`
+ align-self: flex-start;
+ color: ${colors.grayMedium};
+ margin-left: 2px;
`;
-export default FormFieldIcon;
+export { FormFieldIcon };
diff --git a/src/components/form/components/FormField/FormFieldTag.jsx b/src/components/form/components/FormField/FormFieldTag.jsx
index 4a15c0fd..21e53fab 100644
--- a/src/components/form/components/FormField/FormFieldTag.jsx
+++ b/src/components/form/components/FormField/FormFieldTag.jsx
@@ -1,14 +1,18 @@
import styled from "styled-components";
+import { colors } from "../../../../style";
+
const FormFieldTag = styled.span`
display: inline-block;
+ align-self: center;
margin-left: 6px;
padding: 0 5px;
font-size: 13px;
+ line-height: 1.5;
font-weight: 700;
- color: #fff;
+ color: ${colors.whiteSimple};
+ background-color: ${colors.grayMedium};
border-radius: 3px;
- background-color: #aeb9be;
`;
export default FormFieldTag;
diff --git a/src/components/form/components/FormField/index.jsx b/src/components/form/components/FormField/index.jsx
index c26e57e2..545f2142 100644
--- a/src/components/form/components/FormField/index.jsx
+++ b/src/components/form/components/FormField/index.jsx
@@ -7,15 +7,14 @@ import StyledFormField from "./StyledFormField";
import { FormLabel, FormLabelTitle } from "../FormLabel";
import { Tooltip } from "../../../Tooltip";
-import FormFieldIcon from "./FormFieldIcon";
+import { FormFieldIcon } from "./FormFieldIcon";
import FormFieldTag from "./FormFieldTag";
import FormFieldError from "./FormFieldError";
-import { InfoCircle } from "../../../../assets/icons";
-
function FormField(props) {
const {
label,
+ labelType,
render,
name,
type,
@@ -36,7 +35,7 @@ function FormField(props) {
const hasError = Boolean(touched && error);
- const onChange = ev => {
+ const onChange = (ev) => {
if (ev.type) {
field.onChange(ev);
} else {
@@ -69,9 +68,7 @@ function FormField(props) {
return (
tip && (
-
-
-
+
)
);
@@ -96,11 +93,13 @@ function FormField(props) {
{label}
+ {labelType === "bold" && ":"}
{getFormFieldTip()}
{getFormFieldTag()}
@@ -123,6 +122,7 @@ function FormField(props) {
FormField.propTypes = {
name: PropTypes.string.isRequired,
label: PropTypes.oneOfType([PropTypes.node, PropTypes.string]),
+ labelType: PropTypes.oneOf(["bold", "normal"]),
tip: PropTypes.oneOfType([PropTypes.node, PropTypes.string]),
tag: PropTypes.oneOfType([PropTypes.node, PropTypes.string]),
direction: PropTypes.oneOf(["row", "column"]),
@@ -149,6 +149,7 @@ FormField.propTypes = {
FormField.defaultProps = {
direction: "row",
+ labelType: "normal",
showError: true,
};
diff --git a/src/components/form/components/FormInput/FormInput.stories.js b/src/components/form/components/FormInput/FormInput.stories.js
index a7ef3fdf..116075df 100644
--- a/src/components/form/components/FormInput/FormInput.stories.js
+++ b/src/components/form/components/FormInput/FormInput.stories.js
@@ -1,4 +1,5 @@
import React, { useState } from "react";
+
import { storiesOf } from "@storybook/react";
import { boolean } from "@storybook/addon-knobs";
diff --git a/src/components/form/components/FormInput/FormInputUneditable.jsx b/src/components/form/components/FormInput/FormInputUneditable.jsx
new file mode 100644
index 00000000..0444a605
--- /dev/null
+++ b/src/components/form/components/FormInput/FormInputUneditable.jsx
@@ -0,0 +1,13 @@
+import styled from "styled-components";
+
+import { colors } from "../../../../style";
+
+const FormInputUneditable = styled.p`
+ margin: 0;
+ font-weight: bold;
+ font-size: 16px;
+ line-height: 26px;
+ color: ${colors.darkBlack};
+`;
+
+export { FormInputUneditable };
diff --git a/src/components/form/components/FormInput/StyledFormInput.jsx b/src/components/form/components/FormInput/StyledFormInput.jsx
new file mode 100644
index 00000000..bdc595f9
--- /dev/null
+++ b/src/components/form/components/FormInput/StyledFormInput.jsx
@@ -0,0 +1,32 @@
+import styled from "styled-components";
+
+import { colors } from "../../../../style";
+
+const StyledFormInput = styled.input`
+ box-sizing: border-box;
+ padding: 6px 16px;
+ height: 30px;
+ background-color: ${colors.whiteGrayLight};
+ border-radius: 5px;
+ font-size: 14px;
+ line-height: 18px;
+ color: ${colors.darkBlack};
+ border: 1px solid transparent;
+
+ &[type="number"]::-webkit-inner-spin-button,
+ &[type="number"]::-webkit-outer-spin-button {
+ -webkit-appearance: none;
+ margin: 0;
+ }
+
+ &:focus {
+ outline: none;
+ border: 1px solid ${colors.grayMedium};
+ }
+
+ &:disabled {
+ color: ${colors.grayWhite};
+ }
+`;
+
+export { StyledFormInput };
diff --git a/src/components/form/components/FormInput/index.jsx b/src/components/form/components/FormInput/index.jsx
index dd928440..6a606e8c 100644
--- a/src/components/form/components/FormInput/index.jsx
+++ b/src/components/form/components/FormInput/index.jsx
@@ -1,49 +1,54 @@
import React from "react";
import PropTypes from "prop-types";
-import styled from "styled-components";
-import { getTestId } from "../../utils";
-
-const StyledFormInput = styled.input`
- box-sizing: border-box;
- border: 1px solid #9aa7b3;
- border-radius: 3px;
- padding: 2px 10px;
-
- &:focus {
- outline: none;
- }
-
- &[type="number"]::-webkit-inner-spin-button,
- &[type="number"]::-webkit-outer-spin-button {
- -webkit-appearance: none;
- margin: 0;
- }
-`;
+import { StyledFormInput } from "./StyledFormInput";
+import { FormInputUneditable } from "./FormInputUneditable";
-function FormInput(props) {
- const testId = getTestId(props.name, props["data-testid"]);
+import { getTestId } from "../../utils";
+const FormInput = React.forwardRef((props, ref) => {
return (
-
+
);
-}
+});
FormInput.propTypes = {
onChange: PropTypes.func.isRequired,
onBlur: PropTypes.func.isRequired,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
name: PropTypes.string.isRequired,
- placeholder: PropTypes.string,
type: PropTypes.oneOf(["text", "password", "email", "number"]),
disabled: PropTypes.bool,
"data-testid": PropTypes.string,
className: PropTypes.string,
- innerRef: PropTypes.object,
};
FormInput.defaultProps = {
type: "text",
};
-export { FormInput, StyledFormInput };
+FormInput.Password = function (props) {
+ return ;
+};
+
+FormInput.Number = function (props) {
+ return ;
+};
+
+FormInput.Uneditable = function (props) {
+ // eslint-disable-next-line react/prop-types
+ return {props.value};
+};
+
+const StyledFormInputUneditable = FormInputUneditable;
+
+export {
+ FormInput,
+ StyledFormInput,
+ FormInputUneditable,
+ StyledFormInputUneditable,
+};
diff --git a/src/components/form/components/FormInputToggle/FormInputToggle.stories.js b/src/components/form/components/FormInputToggle/FormInputToggle.stories.js
deleted file mode 100644
index 8785ea4c..00000000
--- a/src/components/form/components/FormInputToggle/FormInputToggle.stories.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import React from "react";
-import { storiesOf } from "@storybook/react";
-import { number, text, boolean } from "@storybook/addon-knobs";
-import { action } from "@storybook/addon-actions";
-
-import { useState } from "react";
-
-import { FormInputToggle } from "./index";
-
-storiesOf("Form Components| FormInputToggle", module).add("default", () => {
- function ComponentWrapper() {
- const [value, setValue] = useState("");
-
- return (
- {
- const {
- target: { value },
- } = ev;
- setValue(value);
- action("On change")(ev);
- }}
- onBlur={action("On blur")}
- placeholder={text("placeholder", "enter smth")}
- valuePlaceholder={text("value placeholder", "touch me")}
- disabled={boolean("disabled", false)}
- width={number("width", 200)}
- />
- );
- }
-
- return ;
-});
diff --git a/src/components/form/components/FormInputToggle/FormInputToggleButton.jsx b/src/components/form/components/FormInputToggle/FormInputToggleButton.jsx
deleted file mode 100644
index a5be9285..00000000
--- a/src/components/form/components/FormInputToggle/FormInputToggleButton.jsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import styled from "styled-components";
-
-import { Button } from "../../../Button";
-import { FormUneditableFieldStyles } from "../FormUneditableField/StyledFormUneditableField";
-
-import { colors } from "../../../../themes/colors";
-
-const FormInputToggleButton = styled(Button).attrs(() => ({
- buttonTheme: "reset",
- isRounded: false,
-}))`
- ${FormUneditableFieldStyles}
- /* 11px - ΡΠΌΠ΅ΡΠ΅Π½ΠΈΠ΅ ΠΈΠ½ΠΏΡΡΠ° Π²Π»Π΅Π²ΠΎ */
- /* ΡΠ΅ΠΊΡΡ ΠΏΠ΅ΡΠ΅Π½ΠΎΡΠΈΡΡΡ Π½Π° Π΄ΡΡΠ³ΡΡ ΡΡΡΠΎΠΊΡ */
- /* width: calc(100% - 11px); */
- width: 100%;
- text-decoration: underline dotted;
- border-bottom: 1px dotted
- ${({ hasContent }) => (hasContent ? "transparent" : colors.black)};
-`;
-
-export { FormInputToggleButton };
diff --git a/src/components/form/components/FormInputToggle/StyledFormInputToggle.jsx b/src/components/form/components/FormInputToggle/StyledFormInputToggle.jsx
deleted file mode 100644
index 39b2f5fa..00000000
--- a/src/components/form/components/FormInputToggle/StyledFormInputToggle.jsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import styled from "styled-components";
-
-import { StyledFormInput } from "../FormInput";
-
-const StyledFormInputToggle = styled.span`
- height: 24px;
- line-height: 24px;
- display: inline-block;
- width: ${props => (props.width ? props.width + "px" : "auto")};
- min-width: ${({ hasValue }) => (hasValue ? "auto" : "100px")};
- cursor: pointer;
-
- ${StyledFormInput} {
- width: ${props => (props.width ? "100%" : "auto")};
- position: relative;
- left: -11px;
- }
-`;
-
-export { StyledFormInputToggle };
diff --git a/src/components/form/components/FormInputToggle/__tests__/FormInputToggle.js b/src/components/form/components/FormInputToggle/__tests__/FormInputToggle.js
deleted file mode 100644
index f0cc5fe8..00000000
--- a/src/components/form/components/FormInputToggle/__tests__/FormInputToggle.js
+++ /dev/null
@@ -1,114 +0,0 @@
-import React, { useState } from "react";
-
-import { FormInputToggle } from "../index";
-
-import { fireEvent } from "@testing-library/react";
-import { render } from "../../../../../../test/utils";
-
-const onChangeMock = jest.fn();
-const onBlurMock = jest.fn();
-
-const componentName = "test-form-input";
-
-afterEach(() => {
- onChangeMock.mockClear();
- onBlurMock.mockClear();
-});
-
-afterAll(() => {
- onChangeMock.mockReset();
- onBlurMock.mockReset();
-});
-
-function renderFormInputToggle(props) {
- function FormInputToggleWrapper() {
- const [value, setValue] = useState(null);
-
- return (
-
- );
- }
-
- return render();
-}
-
-describe("FormInputToggle tests", () => {
- test("FormInputToggle should hide button and render input on click", () => {
- const { queryByTestId, getByTestId } = renderFormInputToggle();
-
- expect(queryByTestId(`${componentName}-input`)).not.toBeInTheDocument();
- expect(queryByTestId(`${componentName}-button`)).toBeInTheDocument();
-
- fireEvent.click(getByTestId(componentName));
-
- expect(queryByTestId(`${componentName}-input`)).toBeInTheDocument();
- expect(queryByTestId(`${componentName}-button`)).not.toBeInTheDocument();
- });
-
- test("FormInputToggle should focus on input after render it", () => {
- const { queryByTestId, getByTestId } = renderFormInputToggle();
-
- fireEvent.click(getByTestId(componentName));
-
- expect(queryByTestId(`${componentName}-input`)).toHaveFocus();
- });
-
- test("FormInputToggle should not hide input and render button on click inside", () => {
- const { queryByTestId, getByTestId } = renderFormInputToggle();
-
- fireEvent.click(getByTestId(componentName));
- fireEvent.click(getByTestId(componentName));
-
- expect(queryByTestId(`${componentName}-input`)).toBeInTheDocument();
- expect(queryByTestId(`${componentName}-button`)).not.toBeInTheDocument();
- });
-
- test("FormInputToggle should change input value correctly", () => {
- const { queryByTestId, getByTestId } = renderFormInputToggle();
-
- fireEvent.click(getByTestId(componentName));
- fireEvent.change(getByTestId(`${componentName}-input`), {
- target: { value: "jane_doe" },
- });
-
- expect(queryByTestId(`${componentName}-input`)).toBeInTheDocument();
- expect(queryByTestId(`${componentName}-button`)).not.toBeInTheDocument();
-
- fireEvent.blur(getByTestId(`${componentName}-input`));
-
- expect(queryByTestId(`${componentName}-button`)).toHaveTextContent(
- "jane_doe"
- );
- expect(onChangeMock.mock.calls).toHaveLength(1);
- });
-
- test("FormInputToggle should call onBlur callback correctly", () => {
- const { getByTestId } = renderFormInputToggle();
-
- fireEvent.click(getByTestId(componentName));
- fireEvent.blur(getByTestId(`${componentName}-input`));
-
- expect(onBlurMock.mock.calls).toHaveLength(1);
- });
-
- test("FormInputToggle should hide input and render button on click outside", () => {
- const { queryByTestId, getByTestId } = renderFormInputToggle();
-
- fireEvent.click(getByTestId(componentName));
- fireEvent.blur(getByTestId(`${componentName}-input`));
-
- expect(queryByTestId(`${componentName}-input`)).not.toBeInTheDocument();
- expect(queryByTestId(`${componentName}-button`)).toBeInTheDocument();
- });
-});
diff --git a/src/components/form/components/FormInputToggle/index.jsx b/src/components/form/components/FormInputToggle/index.jsx
deleted file mode 100644
index f63c78be..00000000
--- a/src/components/form/components/FormInputToggle/index.jsx
+++ /dev/null
@@ -1,104 +0,0 @@
-import React, { useEffect } from "react";
-import PropTypes from "prop-types";
-
-import { useRef, useState } from "react";
-
-import { FormInput } from "../FormInput";
-import { StyledFormInputToggle } from "./StyledFormInputToggle";
-import { FormInputToggleButton } from "./FormInputToggleButton";
-
-import { getTestId } from "../../utils";
-
-function FormInputToggle({
- initialOpen,
- name,
- width,
- value,
- onChange,
- onBlur,
- disabled,
- type,
- placeholder,
- buttonText,
- valuePlaceholder,
- className,
- "data-testid": testId,
-}) {
- testId = getTestId(name, testId);
-
- const [isOpen, setIsOpen] = useState(initialOpen);
- const inputRef = useRef(null);
-
- useEffect(() => {
- if (isOpen && inputRef.current) {
- inputRef.current.focus();
- }
- }, [isOpen]);
-
- const buttonContent = buttonText || value || valuePlaceholder;
- const hasValue = value !== undefined && value !== null && value !== "";
-
- return (
- {
- setIsOpen(true);
- }}
- >
- {isOpen ? (
- {
- setIsOpen(false);
- if (onBlur) {
- onBlur(ev);
- }
- }}
- value={hasValue ? value : ""}
- innerRef={inputRef}
- disabled={disabled}
- data-testid={testId + "-input"}
- />
- ) : (
-
- {buttonContent}
-
- )}
-
- );
-}
-
-FormInputToggle.propTypes = {
- onChange: PropTypes.func.isRequired,
- onBlur: PropTypes.func,
- value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
- name: PropTypes.string.isRequired,
- placeholder: PropTypes.string,
- type: PropTypes.oneOf(["text", "password", "email", "number"]),
- initialOpen: PropTypes.bool,
- width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
- disabled: PropTypes.bool,
- className: PropTypes.string,
- valuePlaceholder: PropTypes.string,
- buttonText: PropTypes.string,
- "data-testid": PropTypes.string,
-};
-
-FormInputToggle.defaultProps = {
- type: "text",
- initialOpen: false,
-};
-
-export { FormInputToggle, StyledFormInputToggle };
diff --git a/src/components/form/components/FormLabel/FormLabelTitle.jsx b/src/components/form/components/FormLabel/FormLabelTitle.jsx
index 7e9dc839..dfefb562 100644
--- a/src/components/form/components/FormLabel/FormLabelTitle.jsx
+++ b/src/components/form/components/FormLabel/FormLabelTitle.jsx
@@ -1,8 +1,14 @@
import styled from "styled-components";
+import { colors } from "../../../../style";
+
const FormLabelTitle = styled.span`
+ display: flex;
font-weight: ${({ bold }) => (bold ? 600 : 200)};
cursor: pointer;
+ font-size: 16px;
+ line-height: 26px;
+ color: ${colors.darkBlack};
`;
export { FormLabelTitle };
diff --git a/src/components/form/components/FormLabel/index.jsx b/src/components/form/components/FormLabel/index.jsx
index 49831f17..da600c58 100644
--- a/src/components/form/components/FormLabel/index.jsx
+++ b/src/components/form/components/FormLabel/index.jsx
@@ -1,13 +1,48 @@
-import styled from "styled-components";
+import styled, { css } from "styled-components";
+
+import { FormLabelTitle } from "./FormLabelTitle";
+
+function getDirectionStyles({ direction }) {
+ return direction === "row"
+ ? css`
+ align-items: center;
+ `
+ : css`
+ flex-direction: column;
+
+ ${FormLabelTitle} {
+ margin-bottom: 6px;
+ }
+ `;
+}
+
+function getTypeStyles({ labelType }) {
+ return labelType === "bold"
+ ? css`
+ ${FormLabelTitle} {
+ font-weight: 600;
+ font-size: 12px;
+ line-height: 16px;
+ }
+ `
+ : css`
+ ${FormLabelTitle} {
+ font-size: 16px;
+ line-height: 26px;
+ }
+ `;
+}
const FormLabel = styled.label`
display: inline-flex;
- flex-direction: ${({ direction }) => direction};
- align-items: ${({ direction }) => direction === "row" && "center"};
- height: ${({ height }) => (height ? `${height}px` : "25px")};
+ ${getDirectionStyles}
+ ${getTypeStyles}
`;
const StyledFormLabel = FormLabel;
-export { FormLabelTitle } from "./FormLabelTitle";
-export { FormLabel, StyledFormLabel };
+FormLabel.defaultProps = {
+ direction: "row",
+};
+
+export { FormLabel, StyledFormLabel, FormLabelTitle };
diff --git a/src/components/form/components/FormMultiSelect/FormMultiSelect.stories.js b/src/components/form/components/FormMultiSelect/FormMultiSelect.stories.js
index dd37538c..5723ddd3 100644
--- a/src/components/form/components/FormMultiSelect/FormMultiSelect.stories.js
+++ b/src/components/form/components/FormMultiSelect/FormMultiSelect.stories.js
@@ -1,6 +1,6 @@
import React from "react";
import { storiesOf } from "@storybook/react";
-import { text, boolean, number } from "@storybook/addon-knobs";
+import { text, boolean } from "@storybook/addon-knobs";
import { generateOptions } from "../../../../../test/generate";
@@ -24,7 +24,6 @@ storiesOf("Form Components| FormMultiSelect", module)
value={multiSelectValue}
onChange={setMultiSelectValue}
isLoading={boolean("isLoading", false)}
- width={number("width", 600)}
/>
);
}
@@ -42,7 +41,6 @@ storiesOf("Form Components| FormMultiSelect", module)
value={multiSelectValue}
onChange={setMultiSelectValue}
isLoading={boolean("isLoading", false)}
- width={number("width", 600)}
/>
);
}
diff --git a/src/components/form/components/FormMultiSelect/FormMultiSelectInput.jsx b/src/components/form/components/FormMultiSelect/FormMultiSelectInput.jsx
index 3673309b..bbd43c56 100644
--- a/src/components/form/components/FormMultiSelect/FormMultiSelectInput.jsx
+++ b/src/components/form/components/FormMultiSelect/FormMultiSelectInput.jsx
@@ -1,18 +1,20 @@
import styled from "styled-components";
+import { colors } from "../../../../style";
+
const FormMultiSelectInput = styled.input`
- background-color: transparent;
+ display: inline-block;
width: 350px;
border: none;
outline: none;
- display: inline-block;
- font-weight: 600;
- line-height: 30px;
- color: #444;
+ color: ${colors.darkBlack};
+ font-size: 16px;
+ line-height: 18px;
+ background-color: transparent;
+
::placeholder {
- color: #444;
+ color: ${colors.darkBlack};
}
- padding-left: 3px;
`;
-export default FormMultiSelectInput;
+export { FormMultiSelectInput };
diff --git a/src/components/form/components/FormMultiSelect/FormMultiSelectInputWrapper.jsx b/src/components/form/components/FormMultiSelect/FormMultiSelectInputWrapper.jsx
index 390d8a7d..1f3597cd 100644
--- a/src/components/form/components/FormMultiSelect/FormMultiSelectInputWrapper.jsx
+++ b/src/components/form/components/FormMultiSelect/FormMultiSelectInputWrapper.jsx
@@ -1,9 +1,9 @@
import styled from "styled-components";
-const FormMultiSelectInput = styled.li`
- margin-bottom: 8px;
+const FormMultiSelectInputWrapper = styled.li`
+ margin-bottom: 6px;
width: 130px;
overflow: visible;
`;
-export default FormMultiSelectInput;
+export { FormMultiSelectInputWrapper };
diff --git a/src/components/form/components/FormMultiSelect/FormMultiSelectMenu/FormMultiSelectOption.jsx b/src/components/form/components/FormMultiSelect/FormMultiSelectMenu/FormMultiSelectOption.jsx
index 6255da1e..e6e2e12f 100644
--- a/src/components/form/components/FormMultiSelect/FormMultiSelectMenu/FormMultiSelectOption.jsx
+++ b/src/components/form/components/FormMultiSelect/FormMultiSelectMenu/FormMultiSelectOption.jsx
@@ -1,20 +1,22 @@
-import styled from "styled-components";
+import styled, { css } from "styled-components";
+
+import { colors } from "../../../../../style";
+
+function getStyles({ isHighlighted }) {
+ if (isHighlighted) {
+ return css`
+ background-color: ${colors.whiteGrayLight};
+ `;
+ }
+}
const FormMultiSelectOption = styled.li`
font-size: 16px;
- line-height: 1.5;
- padding: 4px 23px;
+ line-height: 26px;
+ padding: 6px 18px;
cursor: pointer;
- background-color: ${({ isHighlighted }) => {
- if (isHighlighted) return "#3b4b5a";
-
- return "#fff";
- }};
- color: ${({ isHighlighted }) => {
- if (isHighlighted) return "#fff";
-
- return "#263238";
- }};
+ color: ${colors.darkBlack};
+ ${getStyles}
`;
-export default FormMultiSelectOption;
+export { FormMultiSelectOption };
diff --git a/src/components/form/components/FormMultiSelect/FormMultiSelectMenu/index.jsx b/src/components/form/components/FormMultiSelect/FormMultiSelectMenu/index.jsx
index f786bc11..04044dc1 100644
--- a/src/components/form/components/FormMultiSelect/FormMultiSelectMenu/index.jsx
+++ b/src/components/form/components/FormMultiSelect/FormMultiSelectMenu/index.jsx
@@ -1,42 +1,23 @@
import styled, { css } from "styled-components";
+import { colors } from "../../../../../style";
+
const FormMultiSelectMenu = styled.ul`
list-style-type: none;
position: absolute;
- padding-left: 0;
- box-shadow: 0px 0px 80px 0px rgba(162, 182, 189, 0.4);
+ box-shadow: 0px 0px 80px rgba(162, 182, 189, 0.2);
+ border-radius: 4px;
max-height: 288px;
overflow-y: auto;
z-index: 1;
- border-radius: 5px;
- padding: 10px 0px;
- background: white;
- ${({ isUp }) =>
- isUp
- ? css`
- bottom: 85%;
- `
- : css`
- top: 85%;
- `}
+ padding: 12px 0px;
+ background-color: ${colors.whiteSimple};
+
${({ isOpen }) =>
!isOpen &&
css`
visibility: hidden;
`}
-
- &:after {
- content: "";
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- opacity: ${({ isLoading }) => (isLoading ? 1 : 0)};
- visibility: ${({ isLoading }) => (isLoading ? "visible" : "hidden")};
- transition: opacity 200ms ease-in-out, visibility 200ms ease-in-out;
- background-color: rgba(255, 255, 255, 0.6);
- }
`;
-export default FormMultiSelectMenu;
+export { FormMultiSelectMenu };
diff --git a/src/components/form/components/FormMultiSelect/FormMultiSelectTags/FormMultiSelectTagsItem/FormMultiSelectTagsItemCross.jsx b/src/components/form/components/FormMultiSelect/FormMultiSelectTags/FormMultiSelectTagsItem/FormMultiSelectTagsItemCross.jsx
deleted file mode 100644
index bc5491ec..00000000
--- a/src/components/form/components/FormMultiSelect/FormMultiSelectTags/FormMultiSelectTagsItem/FormMultiSelectTagsItemCross.jsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import styled from "styled-components";
-
-import Button from "../../../../../Button";
-import colors from "../../../../../../themes/colors";
-
-const FormMultiSelectTagsItemCross = styled(Button).attrs(() => ({
- buttonTheme: "reset",
-}))`
- margin-left: 4px;
- padding: 0;
- padding-top: 1px;
- padding-left: 5px;
- position: absolute;
- right: 6px;
- background: ${colors.whiteSimple};
-
- &:before {
- font-size: 18px;
- color: #444;
- content: "Γ";
- font-style: normal;
- cursor: pointer;
- }
-`;
-
-export default FormMultiSelectTagsItemCross;
diff --git a/src/components/form/components/FormMultiSelect/FormMultiSelectTags/FormMultiSelectTagsItem/FormMultiSelectTagsItemRemoveButton.jsx b/src/components/form/components/FormMultiSelect/FormMultiSelectTags/FormMultiSelectTagsItem/FormMultiSelectTagsItemRemoveButton.jsx
new file mode 100644
index 00000000..4d3783e1
--- /dev/null
+++ b/src/components/form/components/FormMultiSelect/FormMultiSelectTags/FormMultiSelectTagsItem/FormMultiSelectTagsItemRemoveButton.jsx
@@ -0,0 +1,9 @@
+import styled from "styled-components";
+
+import { Button } from "../../../../../Button";
+
+const FormMultiSelectTagsItemRemoveButton = styled(Button)`
+ padding: 0;
+`;
+
+export { FormMultiSelectTagsItemRemoveButton };
diff --git a/src/components/form/components/FormMultiSelect/FormMultiSelectTags/FormMultiSelectTagsItem/FormMultiSelectTagsItemTitle.jsx b/src/components/form/components/FormMultiSelect/FormMultiSelectTags/FormMultiSelectTagsItem/FormMultiSelectTagsItemTitle.jsx
index 7c176e13..5736bb88 100644
--- a/src/components/form/components/FormMultiSelect/FormMultiSelectTags/FormMultiSelectTagsItem/FormMultiSelectTagsItemTitle.jsx
+++ b/src/components/form/components/FormMultiSelect/FormMultiSelectTags/FormMultiSelectTagsItem/FormMultiSelectTagsItemTitle.jsx
@@ -1,13 +1,17 @@
import styled from "styled-components";
+import { textTrimStyles } from "../../../../../Text/TextTrim";
+import { colors } from "../../../../../../style";
+
const FormMultiSelectTagsItemTitle = styled.span`
- margin-right: 4px;
- vertical-align: middle;
- display: inline-block;
- width: 100%;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
+ font-size: 12px;
+ line-height: 20px;
+ color: ${colors.darkBlack};
+ ${textTrimStyles}
+
+ &:not(:last-child) {
+ margin-right: 4px;
+ }
`;
-export default FormMultiSelectTagsItemTitle;
+export { FormMultiSelectTagsItemTitle };
diff --git a/src/components/form/components/FormMultiSelect/FormMultiSelectTags/FormMultiSelectTagsItem/StyledFormMultiSelectTagsItem.jsx b/src/components/form/components/FormMultiSelect/FormMultiSelectTags/FormMultiSelectTagsItem/StyledFormMultiSelectTagsItem.jsx
index 0a5de734..fbfdbbf0 100644
--- a/src/components/form/components/FormMultiSelect/FormMultiSelectTags/FormMultiSelectTagsItem/StyledFormMultiSelectTagsItem.jsx
+++ b/src/components/form/components/FormMultiSelect/FormMultiSelectTags/FormMultiSelectTagsItem/StyledFormMultiSelectTagsItem.jsx
@@ -1,21 +1,17 @@
import styled from "styled-components";
-const FormMultiSelectTagsItem = styled.li`
- padding-left: 5px;
- padding-right: 5px;
- color: #444;
- background: #fff;
- white-space: nowrap;
- overflow: hidden;
- cursor: pointer;
- border-radius: 6px;
- max-width: 150px;
- margin-bottom: 8px;
- position: relative;
- padding-right: 20px;
- &:not(:last-child) {
- margin-right: 10px;
- }
+import { colors } from "../../../../../../style";
+
+const StyledFormMultiSelectTagsItem = styled.li`
+ box-sizing: border-box;
+ display: flex;
+ align-items: center;
+ overflow: visible;
+ padding: 4px 6px;
+ background-color: ${colors.whiteSimple};
+ border-radius: 4px;
+ height: 20px;
+ max-width: 180px;
`;
-export default FormMultiSelectTagsItem;
+export { StyledFormMultiSelectTagsItem };
diff --git a/src/components/form/components/FormMultiSelect/FormMultiSelectTags/FormMultiSelectTagsItem/index.jsx b/src/components/form/components/FormMultiSelect/FormMultiSelectTags/FormMultiSelectTagsItem/index.jsx
index bfd62ec9..2ec0f70c 100644
--- a/src/components/form/components/FormMultiSelect/FormMultiSelectTags/FormMultiSelectTagsItem/index.jsx
+++ b/src/components/form/components/FormMultiSelect/FormMultiSelectTags/FormMultiSelectTagsItem/index.jsx
@@ -1,9 +1,10 @@
import React from "react";
import PropTypes from "prop-types";
-import StyledFormMultiSelectTagsItem from "./StyledFormMultiSelectTagsItem";
-import FormMultiSelectTagsItemTitle from "./FormMultiSelectTagsItemTitle";
-import FormMultiSelectTagsItemCross from "./FormMultiSelectTagsItemCross";
+import { StyledFormMultiSelectTagsItem } from "./StyledFormMultiSelectTagsItem";
+import { FormMultiSelectTagsItemTitle } from "./FormMultiSelectTagsItemTitle";
+import { FormMultiSelectTagsItemRemoveButton } from "./FormMultiSelectTagsItemRemoveButton";
+import { TimesDelete } from "../../../../../icons";
function FormMultiSelectTagsItem({
onCrossClick,
@@ -13,10 +14,12 @@ function FormMultiSelectTagsItem({
return (
{title}
-
+ >
+
+
);
}
@@ -27,4 +30,4 @@ FormMultiSelectTagsItem.propTypes = {
onCrossClick: PropTypes.func.isRequired,
};
-export default FormMultiSelectTagsItem;
+export { FormMultiSelectTagsItem };
diff --git a/src/components/form/components/FormMultiSelect/FormMultiSelectTags/index.jsx b/src/components/form/components/FormMultiSelect/FormMultiSelectTags/index.jsx
index e9beed69..d0c67309 100644
--- a/src/components/form/components/FormMultiSelect/FormMultiSelectTags/index.jsx
+++ b/src/components/form/components/FormMultiSelect/FormMultiSelectTags/index.jsx
@@ -1,21 +1,21 @@
import styled from "styled-components";
+import { StyledFormMultiSelectTagsItem } from "./FormMultiSelectTagsItem/StyledFormMultiSelectTagsItem";
+
const FormMultiSelectTags = styled.ul`
- box-sizing: border-box;
list-style-type: none;
- padding: 8px;
margin: 0;
- border-radius: 6px;
- cursor: text;
- font-size: 16px;
- font-weight: 600;
- color: #555;
- background: #f3f3f3;
- line-height: 26px;
+ padding: 0;
display: flex;
flex-wrap: wrap;
- padding-left: 8px;
- padding-bottom: 0;
+ cursor: text;
+ margin-bottom: -6px;
+ margin-right: -6px;
+
+ ${StyledFormMultiSelectTagsItem} {
+ margin-bottom: 6px;
+ margin-right: 6px;
+ }
`;
-export default FormMultiSelectTags;
+export { FormMultiSelectTags };
diff --git a/src/components/form/components/FormMultiSelect/StyledFormMultiSelect.jsx b/src/components/form/components/FormMultiSelect/StyledFormMultiSelect.jsx
index 886b5e04..1dee87cf 100644
--- a/src/components/form/components/FormMultiSelect/StyledFormMultiSelect.jsx
+++ b/src/components/form/components/FormMultiSelect/StyledFormMultiSelect.jsx
@@ -1,8 +1,12 @@
import styled from "styled-components";
+import { colors } from "../../../../style";
+
const StyledFormMultiSelect = styled.div`
position: relative;
- width: ${({ width }) => (width ? `${width}px` : "100%")};
+ background-color: ${colors.whiteGrayLight};
+ padding: 12px;
+ border-radius: 4px;
&:after {
content: "";
@@ -18,4 +22,4 @@ const StyledFormMultiSelect = styled.div`
}
`;
-export default StyledFormMultiSelect;
+export { StyledFormMultiSelect };
diff --git a/src/components/form/components/FormMultiSelect/__tests__/FormMultiSelect.js b/src/components/form/components/FormMultiSelect/__tests__/FormMultiSelect.js
index 271557a2..badeeaeb 100644
--- a/src/components/form/components/FormMultiSelect/__tests__/FormMultiSelect.js
+++ b/src/components/form/components/FormMultiSelect/__tests__/FormMultiSelect.js
@@ -32,7 +32,7 @@ function renderFormMultiSelect(props) {
name={componentName}
value={value}
options={options}
- onChange={ev => {
+ onChange={(ev) => {
setValue(ev);
onChangeMock(ev);
}}
@@ -81,11 +81,11 @@ describe("FormMultiSelect tests", () => {
fireEvent.click(getByTestId(`${componentName}-input`));
- optionsToSelect.forEach(option => {
+ optionsToSelect.forEach((option) => {
fireEvent.click(getByTestId(`${componentName}-option-${option.value}`));
});
- optionsToSelect.forEach(option => {
+ optionsToSelect.forEach((option) => {
expect(getByTestId(`${componentName}-tag-${option.value}`));
});
expect(
@@ -104,11 +104,11 @@ describe("FormMultiSelect tests", () => {
fireEvent.click(getByTestId(`${componentName}-input`));
- optionsToSelect.forEach(option => {
+ optionsToSelect.forEach((option) => {
fireEvent.click(getByTestId(`${componentName}-option-${option.value}`));
});
- optionsToSelect.forEach(option => {
+ optionsToSelect.forEach((option) => {
expect(getByTestId(`${componentName}-tag-${option.value}`));
});
diff --git a/src/components/form/components/FormMultiSelect/index.jsx b/src/components/form/components/FormMultiSelect/index.jsx
index a53eaf49..a0c53dbf 100644
--- a/src/components/form/components/FormMultiSelect/index.jsx
+++ b/src/components/form/components/FormMultiSelect/index.jsx
@@ -1,17 +1,17 @@
import React from "react";
import PropTypes from "prop-types";
-import Downshift from "downshift";
-
-import StyledFormMultiSelect from "./StyledFormMultiSelect";
-import FormMultiSelectInput from "./FormMultiSelectInput";
-import FormMultiSelectInputWrapper from "./FormMultiSelectInputWrapper";
-
-import FormMultiSelectMenu from "./FormMultiSelectMenu";
-import FormMultiSelectOption from "./FormMultiSelectMenu/FormMultiSelectOption";
+import { useState, useEffect, useRef } from "react";
+import { usePrevious, useUpdateEffect } from "react-use";
-import FormMultiSelectTags from "./FormMultiSelectTags";
-import FormMultiSelectTagsItem from "./FormMultiSelectTags/FormMultiSelectTagsItem";
+import Downshift from "downshift";
+import { StyledFormMultiSelect } from "./StyledFormMultiSelect";
+import { FormMultiSelectInput } from "./FormMultiSelectInput";
+import { FormMultiSelectInputWrapper } from "./FormMultiSelectInputWrapper";
+import { FormMultiSelectMenu } from "./FormMultiSelectMenu";
+import { FormMultiSelectOption } from "./FormMultiSelectMenu/FormMultiSelectOption";
+import { FormMultiSelectTags } from "./FormMultiSelectTags";
+import { FormMultiSelectTagsItem } from "./FormMultiSelectTags/FormMultiSelectTagsItem";
import {
searchInList,
@@ -23,41 +23,74 @@ import { getTestId } from "../../utils";
const BACKSPACE_KEY_CODE = 8;
-class FormMultiSelect extends React.Component {
- static propTypes = {
- name: PropTypes.string,
- placeholder: PropTypes.string,
- options: PropTypes.array.isRequired,
- onChange: PropTypes.func.isRequired,
- value: PropTypes.array,
- onInputChange: PropTypes.func,
- width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
- menuRef: PropTypes.object,
- isLoading: PropTypes.bool,
- className: PropTypes.string,
- "data-testid": PropTypes.string,
- };
-
- static defaultProps = {
- options: [],
- value: [],
- };
+function FormMultiSelect(props) {
+ const {
+ options,
+ value,
+ onChange,
+ onInputChange,
+ menuRef,
+ width,
+ isLoading,
+ placeholder,
+ className,
+ name,
+ } = props;
+ const testId = getTestId(name, props["data-testid"]);
+ const [selected, setSelected] = useState(value);
+ const prevSelected = usePrevious(selected);
+ const [inputValue, setInputValue] = useState("");
+ const [isOpen, setIsOpen] = useState(false);
+ const inputRef = useRef(null);
+ const tagsRef = useRef(null);
+
+ useEffect(() => {
+ const input = inputRef.current;
+
+ function handleKeyDown(ev) {
+ if (ev.keyCode === BACKSPACE_KEY_CODE && ev.target.value === "") {
+ // Π£Π΄Π°Π»ΡΠ΅ΠΌ ΠΏΠΎΡΠ»Π΅Π΄Π½ΠΈΠΉ ΡΠ»Π΅ΠΌΠ΅Π½Ρ
+ setSelected((selected) => selected.slice(0, selected.length - 1));
+ setIsOpen(false);
+ }
+ }
- state = {
- selected: this.props.value || [],
- inputValue: "",
- isOpen: false,
- isMenuUp: false,
- };
+ if (input) {
+ input.addEventListener("keydown", handleKeyDown);
+ }
- selectRef = React.createRef();
- inputRef = React.createRef();
- menuRef = React.createRef();
- tagsRef = React.createRef();
+ return () => {
+ if (input) {
+ input.removeEventListener("keydown", handleKeyDown);
+ }
+ };
+ }, []);
+
+ const prevInputValue = usePrevious(inputValue);
+ useUpdateEffect(() => {
+ if (prevInputValue !== inputValue) {
+ if (!isOpen) {
+ setIsOpen(true);
+ }
+
+ if (onInputChange) {
+ onInputChange(inputValue);
+ }
+ }
+ }, [inputValue]);
+
+ useUpdateEffect(() => {
+ onChange(selected);
+ }, [selected]);
+ useUpdateEffect(() => {
+ if (value !== prevSelected) {
+ setSelected(value || []);
+ }
+ }, [value]);
- stateReducer = (_, changes) => {
+ function stateReducer(_, changes) {
if (changes.type === Downshift.stateChangeTypes.changeInput) {
- this.setState({ inputValue: changes.inputValue });
+ setInputValue(changes.inputValue);
}
switch (changes.type) {
@@ -70,249 +103,176 @@ class FormMultiSelect extends React.Component {
default:
return changes;
}
- };
-
- componentDidMount() {
- this.inputRef.current.addEventListener("keydown", this.handleKeyDown);
}
- componentWillUnmount() {
- this.inputRef.current.removeEventListener("keydown", this.handleKeyDown);
+ function addItem(item) {
+ setSelected((selected) => [...selected, item]);
}
- handleKeyDown = ev => {
- if (ev.keyCode === BACKSPACE_KEY_CODE && this.state.inputValue === "") {
- this.removeLastItem();
- this.setState({ isOpen: false });
- }
- };
-
- componentDidUpdate(prevProps, prevState) {
- if (prevState.selected !== this.state.selected) {
- this.props.onChange(this.state.selected);
- }
-
- if (prevProps.value !== this.props.value) {
- this.setState({ selected: this.props.value || [] });
- }
-
- if (prevState.inputValue !== this.state.inputValue && !this.state.isOpen) {
- this.setState({ isOpen: true });
- }
-
- const actualMenuRef = this.props.menuRef || this.menuRef;
-
- if (
- !prevState.isOpen &&
- this.state.isOpen &&
- this.selectRef &&
- this.selectRef.current &&
- actualMenuRef &&
- actualMenuRef.current
- ) {
- const selectEl = this.selectRef.current;
- const menuEl = actualMenuRef.current;
-
- const isMenuUp =
- selectEl.getBoundingClientRect().top -
- menuEl.getBoundingClientRect().height >=
- 0;
-
- this.setState({ isMenuUp });
- }
-
- if (prevState.inputValue !== this.state.inputValue) {
- this.props.onInputChange(this.state.inputValue);
- }
+ function removeItem(item) {
+ setSelected((selected) => selected.filter((i) => i !== item));
}
- removeItem = item => {
- this.setState(({ selected }) => ({
- selected: selected.filter(i => i !== item),
- }));
- };
-
- removeLastItem = () => {
- this.setState(({ selected }) => ({
- selected: selected.slice(0, selected.length - 1),
- }));
- };
-
- addItem(item) {
- this.setState(({ selected }) => ({
- selected: [...selected, item],
- }));
+ function handleChange(option) {
+ addItem(option);
+ setInputValue("");
+ setIsOpen(true);
}
- handleChange = option => {
- this.addItem(option);
- this.setState({ inputValue: "", isOpen: true });
- };
-
- handleInputBlur = () => {
- this.setState({ isOpen: false, inputValue: "" });
- };
-
- closeMenu = () => {
- this.setState({ isOpen: false });
- };
-
- toggleMenu = () => {
- this.setState(({ isOpen }) => ({
- isOpen: !isOpen,
- }));
- };
+ function handleInputBlur() {
+ setInputValue("");
+ setIsOpen(false);
+ }
- handleTagsClick = ({ target }) => {
- if (target !== this.tagsRef.current && target !== this.inputRef.current) {
- this.closeMenu();
+ function handleTagsClick({ target }) {
+ if (target !== tagsRef.current && target !== inputRef.current) {
+ setIsOpen(false);
return;
}
- this.inputRef.current.focus();
- this.toggleMenu();
- };
-
- render() {
- const {
- options,
- width,
- placeholder,
- menuRef = this.menuRef,
- name,
- isLoading,
- className,
- } = this.props;
- const { selected, inputValue, isOpen, isMenuUp } = this.state;
-
- const testId = getTestId(name, this.props["data-testid"]);
+ inputRef.current.focus();
+ setIsOpen((isOpen) => !isOpen);
+ }
- return (
- (item && item.label ? item.label : "")}
- stateReducer={this.stateReducer}
- defaultHighlightedIndex={0}
- inputValue={inputValue}
- selectedItem={selected}
- isOpen={isOpen}
- data-testid={testId}
- >
- {({
- getItemProps,
- getMenuProps,
- highlightedIndex,
- selectedItem,
- getInputProps,
- getRootProps,
- }) => {
- return (
- (item && item.label ? item.label : "")}
+ stateReducer={stateReducer}
+ defaultHighlightedIndex={0}
+ inputValue={inputValue}
+ selectedItem={selected}
+ isOpen={isOpen}
+ data-testid={testId}
+ >
+ {({
+ getItemProps,
+ getMenuProps,
+ highlightedIndex,
+ selectedItem,
+ getInputProps,
+ getRootProps,
+ }) => {
+ return (
+
+
-
- {selected.map(selected => {
- const tagName = selected.label || selected;
- const tagValue = selected.value || selected;
-
- return (
- {
- this.removeItem(selected);
- this.inputRef.current.focus();
- }}
- />
- );
- })}
-
- {
+ const tagName = selected.label || selected;
+ const tagValue = selected.value || selected;
+
+ return (
+ {
+ removeItem(selected);
+ inputRef.current.focus();
+ }}
/>
-
- {isOpen &&
- searchInList(
- options,
- inputValue,
- options[0] && options[0].label && ["label"]
- )
- .filter(option => {
- /** ΠΡΠΎΠ²Π΅ΡΠΊΠ° Π½Π°Π»ΠΈΡΠΈΡ ΠΎΠ±ΡΠ΅ΠΊΡΠ° ΠΈΠ»ΠΈ ΠΏΡΠΈΠΌΠΈΡΠΈΠ²Π° Π² ΠΌΠ°ΡΡΠΈΠ²Π΅
- * TODO: ΠΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎ ΡΡΠΎΠΈΡ Π²ΡΠ½Π΅ΡΡΠΈ Π² ΠΎΡΠ΄Π΅Π»ΡΠ½ΡΡ ΡΡΠ½ΠΊΡΠΈΡ
- */
- return !(
- some(selected, option) || selected.includes(option)
- );
- })
- .map((item, index) => {
- const itemValue = hasProperty(item, "value")
- ? item.value
- : item;
- const itemLabel = hasProperty(item, "label")
- ? item.label
- : item;
-
- return (
-
- {getStringShort(itemLabel, 20)}
-
- );
- })}
-
-
-
-
- );
- }}
-
- );
- }
+ );
+ })}
+
+
+
+ {isOpen &&
+ searchInList(
+ options,
+ inputValue,
+ options[0] && options[0].label && ["label"]
+ )
+ .filter((option) => {
+ /** ΠΡΠΎΠ²Π΅ΡΠΊΠ° Π½Π°Π»ΠΈΡΠΈΡ ΠΎΠ±ΡΠ΅ΠΊΡΠ° ΠΈΠ»ΠΈ ΠΏΡΠΈΠΌΠΈΡΠΈΠ²Π° Π² ΠΌΠ°ΡΡΠΈΠ²Π΅
+ * TODO: ΠΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎ ΡΡΠΎΠΈΡ Π²ΡΠ½Π΅ΡΡΠΈ Π² ΠΎΡΠ΄Π΅Π»ΡΠ½ΡΡ ΡΡΠ½ΠΊΡΠΈΡ
+ */
+ return !(
+ some(selected, option) || selected.includes(option)
+ );
+ })
+ .map((item, index) => {
+ const itemValue = hasProperty(item, "value")
+ ? item.value
+ : item;
+ const itemLabel = hasProperty(item, "label")
+ ? item.label
+ : item;
+
+ return (
+
+ {getStringShort(itemLabel, 20)}
+
+ );
+ })}
+
+
+
+
+ );
+ }}
+
+ );
}
+FormMultiSelect.propTypes = {
+ name: PropTypes.string,
+ placeholder: PropTypes.string,
+ options: PropTypes.array.isRequired,
+ onChange: PropTypes.func.isRequired,
+ value: PropTypes.array,
+ onInputChange: PropTypes.func,
+ width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ menuRef: PropTypes.object,
+ isLoading: PropTypes.bool,
+ className: PropTypes.string,
+ "data-testid": PropTypes.string,
+};
+
+FormMultiSelect.defaultProps = {
+ options: [],
+ value: [],
+};
+
FormMultiSelect.Menu = FormMultiSelectMenu;
FormMultiSelect.Option = FormMultiSelectOption;
diff --git a/src/components/form/components/FormRangeSlider/FormRangeSlider.stories.js b/src/components/form/components/FormRangeSlider/FormRangeSlider.stories.js
index 85ef85ef..380140cf 100644
--- a/src/components/form/components/FormRangeSlider/FormRangeSlider.stories.js
+++ b/src/components/form/components/FormRangeSlider/FormRangeSlider.stories.js
@@ -1,6 +1,6 @@
import React from "react";
import { storiesOf } from "@storybook/react";
-import { number, boolean } from "@storybook/addon-knobs";
+import { number } from "@storybook/addon-knobs";
import { useState } from "react";
@@ -19,7 +19,6 @@ storiesOf("Form Components| FormRangeSlider", module).add("default", () => {
minFrom={number("min from", 0)}
minTo={number("min from", 99)}
width={number("width", 200)}
- withClear={boolean("with clear", false)}
/>
);
diff --git a/src/components/form/components/FormRangeSlider/FormRangeSliderResetButton.jsx b/src/components/form/components/FormRangeSlider/FormRangeSliderResetButton.jsx
deleted file mode 100644
index 773f7188..00000000
--- a/src/components/form/components/FormRangeSlider/FormRangeSliderResetButton.jsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import styled, { css } from "styled-components";
-
-import { Button } from "../../../Button";
-
-const FormRangeSliderResetButton = styled(Button).attrs(() => ({
- buttonTheme: "reset",
-}))`
- color: #6b787f;
- font-size: 22px;
- transition: opacity 120ms ease-in-out;
- ${({ isHidden }) =>
- isHidden &&
- css`
- opacity: 0;
- `}
-`;
-
-export { FormRangeSliderResetButton };
diff --git a/src/components/form/components/FormRangeSlider/__tests__/FormRangeSlider.js b/src/components/form/components/FormRangeSlider/__tests__/FormRangeSlider.js
index 210240a2..0948f56f 100644
--- a/src/components/form/components/FormRangeSlider/__tests__/FormRangeSlider.js
+++ b/src/components/form/components/FormRangeSlider/__tests__/FormRangeSlider.js
@@ -27,7 +27,7 @@ function renderFormRangeSlider(props) {
data-testid={componentName}
from={value[0]}
to={value[1]}
- onChange={ev => {
+ onChange={(ev) => {
setValue(ev);
onChangeMock(ev);
}}
@@ -52,12 +52,11 @@ describe("FormRangeSlider tests", () => {
).toHaveTextContent(99);
});
- test("FormRangeSlider should render clear button corretly", () => {
- const { getByTestId } = renderFormRangeSlider({
- withClear: true,
- });
-
- expect(getByTestId(`${componentName}-clear`)).not.toBeVisible();
+ /**
+ * TODO: ΠΠΎΠ΄ΡΠΌΠ°ΡΡ ΠΊΠ°ΠΊ Π½Π°ΠΏΠΈΡΠ°ΡΡ ΡΡΠΎΡ ΡΠ΅ΡΡ
+ */
+ test.skip("FormRangeSlider should change value correctly", () => {
+ const { getByTestId } = renderFormRangeSlider();
/**
* ΡΠ΅Π°Π»ΠΈΠ·Π°ΡΠΈΡ ΡΠ΅ΡΡΠΈΡΠΎΠ²Π°Π½ΠΈΡ drag and drop
@@ -75,7 +74,5 @@ describe("FormRangeSlider tests", () => {
clientX: 1,
clientY: 0,
});
-
- expect(getByTestId(`${componentName}-clear`)).toBeVisible();
});
});
diff --git a/src/components/form/components/FormRangeSlider/index.jsx b/src/components/form/components/FormRangeSlider/index.jsx
index be132119..b66abee2 100644
--- a/src/components/form/components/FormRangeSlider/index.jsx
+++ b/src/components/form/components/FormRangeSlider/index.jsx
@@ -3,41 +3,29 @@ import PropTypes from "prop-types";
import { StyledFormRangeSlider } from "./StyledFormRangeSlider";
import { FormRangeSliderInner } from "./FormRangeSliderInner";
-import { FormRangeSliderResetButton } from "./FormRangeSliderResetButton";
-import { TimesCircle } from "../../../../assets/icons";
import { round } from "lodash";
import { getTestId } from "../../utils";
function formatter(decimals) {
return {
- from: function(value) {
+ from: function (value) {
return `${round(value, decimals)}`;
},
- to: function(value) {
+ to: function (value) {
return `${round(value, decimals)}`;
},
};
}
-function FormRangeSlider({
- from,
- to,
- minFrom,
- maxTo,
- width,
- onChange,
- withClear,
- className,
- "data-testid": testId,
-}) {
- testId = getTestId(name, testId);
-
- const isClearButtonHidden = Boolean(minFrom === from && maxTo === to);
+function FormRangeSlider(props) {
+ const { from, to, minFrom, maxTo, width, onChange, className } = props;
+ const testId = getTestId(props.name, props["data-testid"]);
return (
{
+ onChange={(value) => {
onChange(!value ? value : value.map(Number));
}}
data-testid={`${testId}-slider`}
/>
- {withClear && (
- {
- onChange([minFrom, maxTo]);
- }}
- isHidden={isClearButtonHidden}
- data-testid={`${testId}-clear`}
- >
-
-
- )}
);
}
@@ -75,8 +52,8 @@ FormRangeSlider.propTypes = {
maxTo: PropTypes.number,
width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
onChange: PropTypes.func.isRequired,
- withClear: PropTypes.bool,
className: PropTypes.string,
+ name: PropTypes.string,
"data-testid": PropTypes.string,
};
@@ -85,7 +62,6 @@ FormRangeSlider.defaultProps = {
maxTo: 99,
from: 0,
to: 99,
- withClear: false,
};
export { FormRangeSlider, StyledFormRangeSlider };
diff --git a/src/components/form/components/FormRangeSlider/styles.css b/src/components/form/components/FormRangeSlider/styles.css
index f12ae198..fa6f8789 100644
--- a/src/components/form/components/FormRangeSlider/styles.css
+++ b/src/components/form/components/FormRangeSlider/styles.css
@@ -3,69 +3,47 @@
display: none;
}
-.noUi-handle {
- outline: none;
- border: none;
- background: #a1a1a1;
- border-radius: 22px;
- box-shadow: 0 2px 8px 0px #aaa;
+.noUi-horizontal {
+ height: 4px;
}
+.noUi-connect {
+ background: #3b4b5a;
+}
+.noUi-target {
+ background: #e4e9eb;
+ border-radius: 6px;
-.noUi-horizontal {
- height: 8px;
+ box-shadow: none;
+ border: none;
}
+.noUi-handle {
+ cursor: pointer;
+ background-color: #3b4b5a;
+ border-radius: 50%;
+
+ outline: none;
+ border: none;
+ box-shadow: none;
+}
.noUi-horizontal .noUi-handle {
- /* left: -11px; */
- top: -7px;
- width: 22px;
- height: 22px;
+ top: -3px;
+ right: -5px;
+ width: 10px;
+ height: 10px;
}
.noUi-tooltip {
- font-size: 12px;
font-weight: 600;
+ font-size: 12px;
+ line-height: 16px;
+ color: #3b4b5a;
+
border: none;
}
-
.noUi-horizontal .noUi-tooltip {
transform: translate(-50%, 0);
left: 50%;
bottom: 120%;
padding: 0;
}
-
-html:not([dir="rtl"]) .noUi-horizontal .noUi-handle {
- right: -11px;
-}
-
-.noUi-connects {
- border-radius: 3px;
-}
-
-.noUi-connect {
- background: #27a1ca;
-}
-
-.noUi-target {
- padding: 0 10px;
- background: #ddd;
- border-radius: 2px;
- box-shadow: none;
- border: none;
-}
-.noUi-base:before,
-.noUi-base:after {
- width: 11px;
- content: "";
- position: absolute;
- top: 0;
- height: 100%;
- display: block;
-}
-.noUi-base:before {
- left: -11px;
-}
-.noUi-base:after {
- left: 100%;
-}
diff --git a/src/components/form/components/FormSwitch/FormSwitch.stories.js b/src/components/form/components/FormSwitch/FormSwitch.stories.js
index 18811c37..3f0d0fae 100644
--- a/src/components/form/components/FormSwitch/FormSwitch.stories.js
+++ b/src/components/form/components/FormSwitch/FormSwitch.stories.js
@@ -1,6 +1,5 @@
import React from "react";
import { storiesOf } from "@storybook/react";
-import { select } from "@storybook/addon-knobs";
import { useState } from "react";
@@ -15,8 +14,6 @@ storiesOf("Form Components| FormSwitch", module).add("default", () => {
name="form-switch"
checked={value}
onChange={({ target: { checked } }) => setValue(checked)}
- size={select("Size", ["m", "l"], "m")}
- theme={select("Theme", ["blue", "dark-gray"], "blue")}
/>
);
}
diff --git a/src/components/form/components/FormSwitch/FormSwitchLabel.jsx b/src/components/form/components/FormSwitch/FormSwitchLabel.jsx
index 025b25b4..48d59d4b 100644
--- a/src/components/form/components/FormSwitch/FormSwitchLabel.jsx
+++ b/src/components/form/components/FormSwitch/FormSwitchLabel.jsx
@@ -1,66 +1,39 @@
-import styled, { css } from "styled-components";
+import styled from "styled-components";
-import { colors } from "../../../../themes/colors";
-
-const checkedColors = {
- blue: "#34a7c1",
- "dark-gray": colors.gray,
-};
-const backColors = {
- blue: "#eeeeee",
- "dark-gray": "#DDDDDD",
-};
+import { colors } from "../../../../style";
+import { widths } from "./StyledFormSwitch";
const circleSizes = {
- s: 18,
- m: 22,
+ m: 12,
};
const barHeights = {
- s: 12,
- m: 16,
+ m: 20,
};
-const barWidths = {
- s: 28,
- m: 40,
-};
-
-const checkedStyles = css`
- border-color: #34a7c1;
- background-color: ${({ theme }) => checkedColors[theme]};
- box-shadow: 3px 2px 18px 0 rgba(0, 0, 0, 0.2);
-`;
-
const calcOffset = ({ checked, size }) => {
- return !checked ? "0px" : barWidths[size] - circleSizes[size] / 2 - 3 + "px";
+ return !checked ? "4px" : widths[size] - circleSizes[size] - 4 + "px";
};
const FormSwitchLabel = styled.div`
cursor: pointer;
- padding: 0;
- border: 0 solid ${({ checked }) => (checked ? "#34a7c1" : "#eeeeee")};
- border-radius: 22px;
- background-color: ${({ theme }) => backColors[theme]};
height: ${({ size }) => barHeights[size] + "px"};
- width: ${({ size }) => barWidths[size] + "px"};
+ border-radius: 20px;
+ background-color: ${({ checked }) =>
+ checked ? colors.slate : colors.grayMedium};
&:before {
content: "";
display: block;
- height: ${({ size }) => circleSizes[size] + "px"};
- width: ${({ size }) => circleSizes[size] + "px"};
- margin: -3px;
- background: #9ba7ac;
position: absolute;
- top: 0;
- bottom: 0;
left: ${calcOffset};
- transition: left 120ms ease-in-out;
+ top: 50%;
+ transform: translateY(-50%);
+ height: ${({ size }) => circleSizes[size] + "px"};
+ width: ${({ size }) => circleSizes[size] + "px"};
border-radius: 50%;
- box-shadow: 0 2px 8px 0 #aaa;
-
- ${({ checked }) => (checked ? checkedStyles : "")}
+ background-color: ${colors.whiteSimple};
+ transition: left 120ms ease-in-out;
}
`;
diff --git a/src/components/form/components/FormSwitch/StyledFormSwitch.jsx b/src/components/form/components/FormSwitch/StyledFormSwitch.jsx
index 8291c04a..ad6181fd 100644
--- a/src/components/form/components/FormSwitch/StyledFormSwitch.jsx
+++ b/src/components/form/components/FormSwitch/StyledFormSwitch.jsx
@@ -1,8 +1,7 @@
import styled from "styled-components";
const widths = {
- s: 28,
- m: 40,
+ m: 36,
};
const StyledFormSwitch = styled.label`
@@ -12,4 +11,4 @@ const StyledFormSwitch = styled.label`
user-select: none;
`;
-export { StyledFormSwitch };
+export { StyledFormSwitch, widths };
diff --git a/src/components/form/components/FormSwitch/index.jsx b/src/components/form/components/FormSwitch/index.jsx
index 38c3b713..a78ed243 100644
--- a/src/components/form/components/FormSwitch/index.jsx
+++ b/src/components/form/components/FormSwitch/index.jsx
@@ -10,7 +10,6 @@ import { getTestId } from "../../utils";
function FormSwitch({
name,
size,
- theme,
checked,
onChange,
className,
@@ -37,7 +36,6 @@ function FormSwitch({
@@ -49,14 +47,12 @@ FormSwitch.propTypes = {
checked: PropTypes.bool.isRequired,
name: PropTypes.string.isRequired,
size: PropTypes.oneOf(["s", "m"]),
- theme: PropTypes.oneOf(["blue", "dark-gray"]),
className: PropTypes.string,
"data-testid": PropTypes.string,
};
FormSwitch.defaultProps = {
size: "m",
- theme: "blue",
};
-export { FormSwitch, StyledFormSwitch };
+export { FormSwitch, StyledFormSwitch, FormSwitchLabel };
diff --git a/src/components/form/components/index.js b/src/components/form/components/index.js
index d09e62a2..0f7e27a7 100644
--- a/src/components/form/components/index.js
+++ b/src/components/form/components/index.js
@@ -1,7 +1,6 @@
export * from "./FormField";
export * from "./FormLabel";
export * from "./FormInput";
-export * from "./FormInputToggle";
export * from "./FormSwitch";
export * from "./FormDropdown";
export * from "./FormCheckbox";
diff --git a/src/components/form/hooks/use-form-api-errors.js b/src/components/form/hooks/use-form-api-errors.js
index 63cdc7af..7deb5f70 100644
--- a/src/components/form/hooks/use-form-api-errors.js
+++ b/src/components/form/hooks/use-form-api-errors.js
@@ -2,6 +2,8 @@ import { useEffect } from "react";
export function useFormApiErrors(apiErrors, setErrors) {
useEffect(() => {
- setErrors(apiErrors);
+ if (setErrors) {
+ setErrors(apiErrors);
+ }
}, [apiErrors]);
}
diff --git a/src/components/form/layouts/FormActions/index.jsx b/src/components/form/layouts/FormActions/index.jsx
deleted file mode 100644
index 8bb51fc9..00000000
--- a/src/components/form/layouts/FormActions/index.jsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import styled from "styled-components";
-
-import { StyledButton } from "../../../Button";
-
-const FormActions = styled.div`
- display: flex;
-
- ${StyledButton} {
- height: 38px;
- line-height: 38px;
- width: 104px;
- padding: 0 24px;
-
- &:not(:last-child) {
- margin-right: 8px;
- }
- }
-`;
-
-export { FormActions };
diff --git a/src/components/form/layouts/FormFieldGroup/FormFieldGroupTitle.jsx b/src/components/form/layouts/FormFieldGroup/FormFieldGroupTitle.jsx
index bd44f304..edcbab26 100644
--- a/src/components/form/layouts/FormFieldGroup/FormFieldGroupTitle.jsx
+++ b/src/components/form/layouts/FormFieldGroup/FormFieldGroupTitle.jsx
@@ -2,6 +2,7 @@ import styled from "styled-components";
const FormFieldGroupTitle = styled.h3`
font-size: 15px;
+ line-height: 20px;
font-weight: 700;
text-transform: uppercase;
margin-top: 0;
diff --git a/src/components/form/layouts/index.js b/src/components/form/layouts/index.js
index 9d601abd..6b2c24cf 100644
--- a/src/components/form/layouts/index.js
+++ b/src/components/form/layouts/index.js
@@ -1,2 +1 @@
export * from "./FormFieldGroup";
-export * from "./FormActions";
diff --git a/src/components/icons/ArrowRight.jsx b/src/components/icons/ArrowRight.jsx
new file mode 100644
index 00000000..e10858b0
--- /dev/null
+++ b/src/components/icons/ArrowRight.jsx
@@ -0,0 +1,33 @@
+import React from "react";
+import PropTypes from "prop-types";
+
+function ArrowRight({ size, className }) {
+ return (
+
+ );
+}
+
+ArrowRight.propTypes = {
+ size: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
+ className: PropTypes.string,
+};
+
+ArrowRight.defaultProps = {
+ size: 12,
+};
+
+export default ArrowRight;
diff --git a/src/assets/icons/Asterisk.jsx b/src/components/icons/Asterisk.jsx
similarity index 69%
rename from src/assets/icons/Asterisk.jsx
rename to src/components/icons/Asterisk.jsx
index f63bc3e0..b6cf4ccb 100644
--- a/src/assets/icons/Asterisk.jsx
+++ b/src/components/icons/Asterisk.jsx
@@ -1,7 +1,7 @@
import styled, { css } from "styled-components";
-import { Asterisk } from "styled-icons/fa-solid/Asterisk";
-import { spin } from "../animations";
+import { Asterisk } from "@styled-icons/fa-solid/Asterisk";
+import { spin } from "../../style";
const AsteriskIcon = styled(Asterisk)`
animation: ${({ isSpin }) =>
diff --git a/src/components/icons/Check.jsx b/src/components/icons/Check.jsx
new file mode 100644
index 00000000..3b289088
--- /dev/null
+++ b/src/components/icons/Check.jsx
@@ -0,0 +1,33 @@
+import React from "react";
+import PropTypes from "prop-types";
+
+function Check({ size, className }) {
+ return (
+
+ );
+}
+
+Check.propTypes = {
+ size: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
+ className: PropTypes.string,
+};
+
+Check.defaultProps = {
+ size: 10,
+};
+
+export default Check;
diff --git a/src/components/icons/Gear.jsx b/src/components/icons/Gear.jsx
new file mode 100644
index 00000000..f99d2955
--- /dev/null
+++ b/src/components/icons/Gear.jsx
@@ -0,0 +1,37 @@
+import React from "react";
+import PropTypes from "prop-types";
+
+function Gear({ size, className }) {
+ return (
+
+ );
+}
+
+Gear.propTypes = {
+ size: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
+ className: PropTypes.string,
+};
+
+Gear.defaultProps = {
+ size: 10,
+};
+
+export default Gear;
diff --git a/src/components/icons/GearOutline.jsx b/src/components/icons/GearOutline.jsx
new file mode 100644
index 00000000..5ff3064c
--- /dev/null
+++ b/src/components/icons/GearOutline.jsx
@@ -0,0 +1,36 @@
+import React from "react";
+import PropTypes from "prop-types";
+
+function GearOutline({ size, className }) {
+ return (
+
+ );
+}
+
+GearOutline.propTypes = {
+ size: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
+ className: PropTypes.string,
+};
+
+GearOutline.defaultProps = {
+ size: 10,
+};
+
+export default GearOutline;
diff --git a/src/components/icons/InfoCircleColor.jsx b/src/components/icons/InfoCircleColor.jsx
new file mode 100644
index 00000000..5bddc798
--- /dev/null
+++ b/src/components/icons/InfoCircleColor.jsx
@@ -0,0 +1,37 @@
+import React from "react";
+import PropTypes from "prop-types";
+
+function InfoCircleColor({ size, className }) {
+ return (
+
+ );
+}
+
+InfoCircleColor.propTypes = {
+ size: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
+ className: PropTypes.string,
+};
+
+InfoCircleColor.defaultProps = {
+ size: 10,
+};
+
+export default InfoCircleColor;
diff --git a/src/components/icons/Pencil.jsx b/src/components/icons/Pencil.jsx
new file mode 100644
index 00000000..33985fcb
--- /dev/null
+++ b/src/components/icons/Pencil.jsx
@@ -0,0 +1,35 @@
+import React from "react";
+import PropTypes from "prop-types";
+
+function Pencil({ size, className }) {
+ return (
+
+ );
+}
+
+Pencil.propTypes = {
+ size: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
+ className: PropTypes.string,
+};
+
+Pencil.defaultProps = {
+ size: 10,
+};
+
+export default Pencil;
diff --git a/src/components/icons/QRcode.jsx b/src/components/icons/QRcode.jsx
new file mode 100644
index 00000000..06860c69
--- /dev/null
+++ b/src/components/icons/QRcode.jsx
@@ -0,0 +1,31 @@
+import React from "react";
+import PropTypes from "prop-types";
+
+function QRcode({ size, className }) {
+ return (
+
+ );
+}
+
+QRcode.propTypes = {
+ size: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
+ className: PropTypes.string,
+};
+
+QRcode.defaultProps = {
+ size: 10,
+};
+
+export default QRcode;
diff --git a/src/components/icons/Search.jsx b/src/components/icons/Search.jsx
new file mode 100644
index 00000000..7c62f0a8
--- /dev/null
+++ b/src/components/icons/Search.jsx
@@ -0,0 +1,38 @@
+import React from "react";
+import PropTypes from "prop-types";
+
+function Search({ size, className }) {
+ return (
+
+ );
+}
+
+Search.propTypes = {
+ size: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
+ className: PropTypes.string,
+};
+
+Search.defaultProps = {
+ size: 10,
+};
+
+export default Search;
diff --git a/src/assets/icons/Sync.jsx b/src/components/icons/Sync.jsx
similarity index 68%
rename from src/assets/icons/Sync.jsx
rename to src/components/icons/Sync.jsx
index df570461..6974033d 100644
--- a/src/assets/icons/Sync.jsx
+++ b/src/components/icons/Sync.jsx
@@ -1,7 +1,7 @@
import styled, { css } from "styled-components";
-import { SyncAlt } from "styled-icons/fa-solid/SyncAlt";
-import { spin } from "../animations";
+import { SyncAlt } from "@styled-icons/fa-solid/SyncAlt";
+import { spin } from "../../style";
const SyncIcon = styled(SyncAlt)`
animation: ${({ isSpin }) =>
diff --git a/src/components/icons/TimesDelete.jsx b/src/components/icons/TimesDelete.jsx
new file mode 100644
index 00000000..3cd1bf80
--- /dev/null
+++ b/src/components/icons/TimesDelete.jsx
@@ -0,0 +1,33 @@
+import React from "react";
+import PropTypes from "prop-types";
+
+function TimesDelete({ size, className }) {
+ return (
+
+ );
+}
+
+TimesDelete.propTypes = {
+ size: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
+ className: PropTypes.string,
+};
+
+TimesDelete.defaultProps = {
+ size: 10,
+};
+
+export default TimesDelete;
diff --git a/src/components/icons/TimesDeleteBold.jsx b/src/components/icons/TimesDeleteBold.jsx
new file mode 100644
index 00000000..45a77770
--- /dev/null
+++ b/src/components/icons/TimesDeleteBold.jsx
@@ -0,0 +1,33 @@
+import React from "react";
+import PropTypes from "prop-types";
+
+function TimesDeleteBold({ size, className }) {
+ return (
+
+ );
+}
+
+TimesDeleteBold.propTypes = {
+ size: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
+ className: PropTypes.string,
+};
+
+TimesDeleteBold.defaultProps = {
+ size: 10,
+};
+
+export default TimesDeleteBold;
diff --git a/src/components/icons/index.js b/src/components/icons/index.js
new file mode 100644
index 00000000..5418e176
--- /dev/null
+++ b/src/components/icons/index.js
@@ -0,0 +1,56 @@
+export {
+ Bolt,
+ Eye,
+ EyeSlash,
+ Camera,
+ Video,
+ VideoSlash,
+ User,
+ UserPlus,
+ UserTimes,
+ Times,
+ Plus,
+ Exclamation,
+ Play,
+ ArrowUp,
+ AngleRight,
+ ArrowDown,
+ ArrowLeft,
+ InfoCircle,
+ Clone,
+ CalendarAlt,
+ Ban,
+ Trash,
+} from "@styled-icons/fa-solid";
+
+export {
+ RightArrow as BoxRightArrow,
+ LeftArrow as BoxLeftArrow,
+ Pencil as BoxPencil,
+} from "@styled-icons/boxicons-solid";
+
+export {
+ TimesCircle,
+ ArrowAltCircleDown,
+ ArrowAltCircleRight,
+} from "@styled-icons/fa-regular";
+
+export {
+ KeyboardArrowRight,
+ KeyboardArrowLeft,
+ PlaylistAdd,
+ PlaylistAddCheck,
+} from "@styled-icons/material";
+
+export { default as ArrowRight } from "./ArrowRight";
+export { default as InfoCircleColor } from "./InfoCircleColor";
+export { default as Pencil } from "./Pencil";
+export { default as TimesDelete } from "./TimesDelete";
+export { default as TimesDeleteBold } from "./TimesDeleteBold";
+export { default as Check } from "./Check";
+export { default as Sync } from "./Sync";
+export { default as Asterisk } from "./Asterisk";
+export { default as Search } from "./Search";
+export { default as Gear } from "./Gear";
+export { default as GearOutline } from "./GearOutline";
+export { default as QRcode } from "./QRcode";
diff --git a/src/components/index.js b/src/components/index.js
index 6688a63c..41558c46 100644
--- a/src/components/index.js
+++ b/src/components/index.js
@@ -1,12 +1,11 @@
export * from "./Badge";
export * from "./UIBadge";
+export * from "./Breadcrumbs";
export * from "./Button";
export * from "./ButtonLink";
export * from "./CardSmall";
export * from "./CopyItem";
-export * from "./DeleteSure";
export * from "./DeleteSureButton";
-export * from "./DynamicList";
export * from "./Entries";
export * from "./ErrorBoundary";
export * from "./ErrorPageBoundary";
@@ -18,6 +17,9 @@ export * from "./IdCopy";
export * from "./IdFormat";
export * from "./InfiniteScroll";
export * from "./InfiniteDropdown";
+export * from "./InfiniteMultiSelect";
+export * from "./InfinitePageList";
+export * from "./InfoCard";
export * from "./ImageCentered";
export * from "./LazyImage";
export * from "./ListItemPopup";
@@ -26,7 +28,6 @@ export * from "./Login";
export * from "./Notice";
export * from "./notification";
export * from "./PageCard";
-export * from "./PageFiltersList";
export * from "./PageLoader";
export * from "./PageWrapper";
export * from "./PersonsGroup";
@@ -35,18 +36,16 @@ export * from "./PopupConfirm";
export * from "./RouteSection";
export * from "./Search";
export * from "./Segment";
-export * from "./SelectableList";
export * from "./ShownControl";
export * from "./Slider";
export * from "./Spinner";
export * from "./Text";
export * from "./Tooltip";
-export * from "./WithCurrentOpenItem";
export * from "./Tabs";
export * from "./Stepper";
+export * from "./SegmentedTabs";
export * from "./Value";
export * from "./ValueSpan";
-export * from "./FaceSize";
export * from "./PopupContainer";
export * from "./GlobalStyles";
export * from "./I18nProvider";
diff --git a/src/components/notification/NotificationCloseButton.jsx b/src/components/notification/NotificationCloseButton.jsx
index e1360c2c..0bcd755e 100644
--- a/src/components/notification/NotificationCloseButton.jsx
+++ b/src/components/notification/NotificationCloseButton.jsx
@@ -1,10 +1,8 @@
import styled from "styled-components";
-import Button from "../Button";
+import { Button } from "../Button";
-const NotificationCloseButton = styled(Button).attrs(() => ({
- buttonTheme: "reset",
-}))`
+const NotificationCloseButton = styled(Button)`
position: absolute;
top: 0px;
right: 15px;
diff --git a/src/components/notification/NotificationContent.jsx b/src/components/notification/NotificationContent.jsx
index fc044c29..09d4df53 100644
--- a/src/components/notification/NotificationContent.jsx
+++ b/src/components/notification/NotificationContent.jsx
@@ -3,7 +3,7 @@ import PropTypes from "prop-types";
import StyledNotificationContent from "./StyledNotificationContent";
-import { Bolt } from "../../assets/icons";
+import { Bolt } from "../icons";
NotificationContent.propTypes = {
message: PropTypes.string.isRequired,
diff --git a/src/components/notification/index.jsx b/src/components/notification/index.jsx
index 8bb04389..547f970b 100644
--- a/src/components/notification/index.jsx
+++ b/src/components/notification/index.jsx
@@ -4,7 +4,7 @@ import React from "react";
import NotificationContent from "./NotificationContent";
import NotificationCloseButton from "./NotificationCloseButton";
-import { Times } from "../../assets/icons";
+import { Times } from "../icons";
import "./style.css";
@@ -26,7 +26,7 @@ Notification.newInstance(
),
},
- n => (notification = n)
+ (n) => (notification = n)
);
export function open({ message, description, type, duration }) {
diff --git a/src/components/notification/style.css b/src/components/notification/style.css
index 78f61a45..5a2f7202 100644
--- a/src/components/notification/style.css
+++ b/src/components/notification/style.css
@@ -8,7 +8,6 @@
line-height: 1.5;
list-style: none;
font-feature-settings: "tnum";
- position: fixed;
z-index: 1010;
width: 384px;
max-width: calc(100vw - 32px);
@@ -30,7 +29,6 @@
}
.ui-notification-fade-leave {
- animation-duration: 0.24s;
animation-timing-function: ease-in-out;
animation-fill-mode: both;
diff --git a/src/hooks/index.js b/src/hooks/index.js
index 9a155eff..24938af7 100644
--- a/src/hooks/index.js
+++ b/src/hooks/index.js
@@ -4,4 +4,8 @@ export * from "./use-pagination";
export * from "./use-timeout";
export * from "./use-popup";
export * from "./use-position-popup";
+export * from "./use-infinite-menu";
+export * from "./use-redirect-on-error";
+export * from "./use-scroll-direction";
+export * from "./use-selectable-list";
export { useTranslation, withTranslation } from "react-i18next";
diff --git a/src/hooks/use-copy-to-clipboard.js b/src/hooks/use-copy-to-clipboard.js
index 251dce7c..6763162c 100644
--- a/src/hooks/use-copy-to-clipboard.js
+++ b/src/hooks/use-copy-to-clipboard.js
@@ -6,7 +6,7 @@ const isCopyAvailable =
function useCopyToClipboard() {
const [isCopied, setIsCopied] = useState(false);
- const copyToClipboard = text => {
+ const copyToClipboard = (text) => {
setIsCopied(false);
if (typeof text == "string" || typeof text == "number") {
diff --git a/src/hooks/use-infinite-menu.js b/src/hooks/use-infinite-menu.js
new file mode 100644
index 00000000..ad5ba0c8
--- /dev/null
+++ b/src/hooks/use-infinite-menu.js
@@ -0,0 +1,37 @@
+import { useState, useEffect } from "react";
+import { useListFetch } from "./use-list-fetch";
+
+function useInfiniteMenu({ limit, hasNext, fetchOptions }) {
+ const [isListEnds, setIsListEnds] = useState(!hasNext);
+
+ const { pagination, setPagination, setFetchParams } = useListFetch({
+ fetchList: (params) => {
+ fetchOptions({ ...params, meta: { clearList: params.offset === 0 } });
+ },
+ pagination: { limit, offset: 0 },
+ });
+
+ useEffect(() => {
+ setIsListEnds(!hasNext);
+ }, [hasNext]);
+
+ function searchOptions(value) {
+ setFetchParams({
+ q: value,
+ offset: 0,
+ });
+ }
+
+ function fetchNext() {
+ if (!isListEnds) {
+ setPagination({ offset: pagination.offset + pagination.limit });
+ }
+ }
+
+ return {
+ searchOptions,
+ fetchNext,
+ };
+}
+
+export { useInfiniteMenu };
diff --git a/src/hooks/use-list-fetch.js b/src/hooks/use-list-fetch.js
index 4e706f5d..31259c8b 100644
--- a/src/hooks/use-list-fetch.js
+++ b/src/hooks/use-list-fetch.js
@@ -26,11 +26,21 @@ function useFetchParams(initialFetchParams = {}) {
}
export function useListFetch({
+ /**
+ * ΠΡΠΈ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠΈ Ρ
ΡΠΊΠ°
+ * Π²ΠΎ ΠΈΠ·Π±Π΅ΠΆΠ°Π½ΠΈΠ΅ ΠΌΠ½ΠΎΠΆΠ΅ΡΡΠ²Π΅Π½Π½Π³ΠΎ ΠΏΠ΅ΡΠ΅ΡΠΎΠ·Π΄Π°Π½ΠΈΡ debouncedFetchList
+ * ΡΠ»Π΅Π΄ΡΠ΅Ρ Π²ΡΠ΅Π³Π΄Π° ΠΏΠ΅ΡΠ΅Π΄Π°Π²Π°ΡΡ ΡΡΡΠ»ΠΊΡ Π½Π° ΠΎΠ΄Π½Ρ ΠΈ ΡΡ ΠΆΠ΅ fetchList
+ * ΠΎΠ±Π½ΠΎΠ²ΠΈΡΡ fetchList ΡΠ»Π΅Π΄ΡΠ΅Ρ ΡΠΎΠ»ΡΠΊΠΎ ΡΠΎΠ³Π΄Π°, ΠΊΠΎΠ³Π΄Π° Π½ΡΠΆΠ½ΠΎ
+ * ΠΎΠ±Π½ΠΎΠ²ΠΈΡΡ Π΅Π΅ Π·Π°ΠΌΡΠΊΠ°Π½ΠΈΠ΅
+ * */
+
fetchList,
pagination: defaultPagination = { limit: 10, offset: 0 },
searchQuery: initialSearchQuery = "",
clearList = true,
searchQueryHook = useSearchQuery,
+ fetchOnInitial = true,
+ onFetchParamsChange,
}) {
if (!(fetchList instanceof Function)) {
console.warn("You did not specify fetchList function");
@@ -48,7 +58,7 @@ export function useListFetch({
const prevFetchParams = usePrevious(fetchParams);
- const debouncedFetchList = useCallback(debounce(fetchList, 500), []);
+ const debouncedFetchList = useCallback(debounce(fetchList, 500), [fetchList]);
function fetchListWithParams(hasDebounce) {
const params = { ...fetchParams, meta: { clearList, hasDebounce } };
@@ -69,6 +79,14 @@ export function useListFetch({
}
useDeepCompareEffect(() => {
+ if (onFetchParamsChange) {
+ onFetchParamsChange(prevFetchParams, fetchParams);
+ }
+
+ if (prevFetchParams === undefined && !fetchOnInitial) {
+ return;
+ }
+
const hasDebounce = prevFetchParams && prevFetchParams.q !== fetchParams.q;
if (fetchParams.q !== searchQuery) {
@@ -92,6 +110,10 @@ export function useListFetch({
setFetchParams({ limit: pagination.limit, offset: pagination.offset });
}, [pagination]);
+ function changeOffset(offset) {
+ setPagination({ ...pagination, offset });
+ }
+
return {
pagination,
setPagination,
@@ -104,5 +126,8 @@ export function useListFetch({
setSearchQuery,
fetchListWithParams,
+
+ offset: pagination.offset,
+ changeOffset,
};
}
diff --git a/src/hooks/use-pagination.js b/src/hooks/use-pagination.js
index b58e5140..a7c55bc9 100644
--- a/src/hooks/use-pagination.js
+++ b/src/hooks/use-pagination.js
@@ -5,7 +5,7 @@ const calculateSelectedPage = (offset, limit) => {
return offset / limit + 1 || 1;
};
-export const usePagination = props => {
+export const usePagination = (props) => {
const { limit, offset, totalCount } = props;
const fullPaginationNumbersLength = Math.ceil(totalCount / limit || 0);
diff --git a/src/hooks/use-popup.js b/src/hooks/use-popup.js
index 0eb50983..2a4b43a7 100644
--- a/src/hooks/use-popup.js
+++ b/src/hooks/use-popup.js
@@ -24,7 +24,8 @@ function usePopup(params) {
params.onOpen(targetEl);
}
},
- bindTo: document && document.getElementById("app-container"),
+ bindTo:
+ params.bindTo || (document && document.getElementById("app-container")),
});
const [targetParams, setTargetParams] = useState({
diff --git a/src/hooks/use-redirect-on-error.js b/src/hooks/use-redirect-on-error.js
new file mode 100644
index 00000000..f362e67c
--- /dev/null
+++ b/src/hooks/use-redirect-on-error.js
@@ -0,0 +1,11 @@
+import { useEffect } from "react";
+
+function useRedirectOnError(error, redirect) {
+ useEffect(() => {
+ if (error && redirect) {
+ redirect();
+ }
+ }, [error]);
+}
+
+export { useRedirectOnError };
diff --git a/src/hooks/use-scroll-direction.js b/src/hooks/use-scroll-direction.js
new file mode 100644
index 00000000..4a18efb9
--- /dev/null
+++ b/src/hooks/use-scroll-direction.js
@@ -0,0 +1,30 @@
+import { useState, useEffect, useRef } from "react";
+import { useWindowScroll } from "react-use";
+
+import { throttle } from "lodash-es";
+
+function useScrollDirection() {
+ const { y } = useWindowScroll();
+ const prevY = useRef(y);
+ const [directionY, setDirectionY] = useState(null);
+ function handleScroll(prevY, y) {
+ if (prevY !== undefined) {
+ setDirectionY(prevY < y ? "bottom" : "top");
+ }
+ }
+ const throttledScroll = useRef(throttle(handleScroll, 300));
+
+ useEffect(() => {
+ throttledScroll.current(prevY.current, y);
+ if (Math.abs(prevY.current - y) > 50) {
+ prevY.current = y;
+ }
+ }, [y]);
+
+ return {
+ y,
+ directionY,
+ };
+}
+
+export { useScrollDirection };
diff --git a/src/hooks/use-selectable-list.js b/src/hooks/use-selectable-list.js
new file mode 100644
index 00000000..b3148a3f
--- /dev/null
+++ b/src/hooks/use-selectable-list.js
@@ -0,0 +1,93 @@
+import { useState } from "react";
+import { usePrevious, useUpdateEffect } from "react-use";
+
+import { uniq, isEqual } from "lodash-es";
+import { hasProperty } from "../utils/helpers";
+
+function useSelectableList({ options, value, onChange }) {
+ const [selected, setSelected] = useState(value || []);
+ const prevSelected = usePrevious(selected);
+ const [lastChecked, setLastChecked] = useState(null);
+
+ function getOptionPresentation(option) {
+ return hasProperty(option, "value") ? String(option.value) : String(option);
+ }
+
+ useUpdateEffect(() => {
+ if (onChange && !isEqual(prevSelected, selected)) {
+ onChange(selected);
+ }
+ }, [selected]);
+
+ useUpdateEffect(() => {
+ setSelected(value);
+ }, [value]);
+
+ function selectAll() {
+ setSelected(options.map(getOptionPresentation));
+ }
+
+ function deselectAll() {
+ setSelected([]);
+ }
+
+ function toggleSelected(option) {
+ const optionPresentation = getOptionPresentation(option);
+
+ setSelected((selected) =>
+ selected.includes(optionPresentation)
+ ? selected.filter((sel) => optionPresentation !== sel)
+ : selected.concat(optionPresentation)
+ );
+ }
+
+ function toggleIntermediateOptions(checkFrom, checkTo, isCheck) {
+ const stringOptions = options.map(getOptionPresentation);
+
+ if (stringOptions.indexOf(checkFrom) > stringOptions.indexOf(checkTo)) {
+ [checkTo, checkFrom] = [checkFrom, checkTo];
+ }
+
+ const hasToCheck = stringOptions
+ .filter(
+ (_, key) =>
+ stringOptions.indexOf(checkFrom) < key &&
+ key < stringOptions.indexOf(checkTo)
+ )
+ .concat([checkFrom, checkTo]);
+
+ setSelected((selected) =>
+ isCheck
+ ? uniq(selected.concat(hasToCheck))
+ : selected.filter((option) => !hasToCheck.includes(option))
+ );
+ }
+
+ function onCheckboxChange(event) {
+ const { name: option } = event.target;
+ toggleSelected(option);
+ setLastChecked(option);
+
+ const { shiftKey } = event.nativeEvent;
+ const isCheck = !selected.includes(option);
+ if (shiftKey && option !== lastChecked) {
+ toggleIntermediateOptions(lastChecked, option, isCheck);
+ }
+ }
+
+ const hasOptions = options && options.length > 0;
+
+ return {
+ selected,
+ selectAll,
+ deselectAll,
+ toggleSelected,
+ onCheckboxChange,
+ isAllSelected: hasOptions && selected.length === options.length,
+ isAllDeselected: hasOptions && selected.length === 0,
+ hasOptions: options.length !== 0,
+ hasSelected: selected.length !== 0,
+ };
+}
+
+export { useSelectableList };
diff --git a/src/index.js b/src/index.js
index ef8e0203..d18af30a 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,5 +1,5 @@
export * from "./assets";
-export * from "./themes";
-export * from "./hooks";
export * from "./components";
+export * from "./hooks";
+export * from "./style";
export * from "./utils";
diff --git a/src/locales/en.js b/src/locales/en.js
deleted file mode 100644
index e2ebf59c..00000000
--- a/src/locales/en.js
+++ /dev/null
@@ -1,7 +0,0 @@
-export const en = {
- translation: {
- Apply: "Apply",
- Clear: "Clear",
- Reset: "Reset",
- },
-};
diff --git a/src/locales/index.js b/src/locales/index.js
deleted file mode 100644
index 6a8fd3c8..00000000
--- a/src/locales/index.js
+++ /dev/null
@@ -1,9 +0,0 @@
-import { en } from "./en";
-import { ru } from "./ru";
-
-const locales = {
- en,
- ru,
-};
-
-export default locales;
diff --git a/src/locales/ru.js b/src/locales/ru.js
deleted file mode 100644
index 6ac2c1aa..00000000
--- a/src/locales/ru.js
+++ /dev/null
@@ -1,7 +0,0 @@
-export const ru = {
- translation: {
- Apply: "ΠΡΠΈΠΌΠ΅Π½ΠΈΡΡ",
- Clear: "ΠΡΠΈΡΡΠΈΡΡ",
- Reset: "Π‘Π±ΡΠΎΡΠΈΡΡ",
- },
-};
diff --git a/src/assets/animations/fade-enter.jsx b/src/style/animations/fade-enter.jsx
similarity index 81%
rename from src/assets/animations/fade-enter.jsx
rename to src/style/animations/fade-enter.jsx
index c2e20729..a9b6dbf0 100644
--- a/src/assets/animations/fade-enter.jsx
+++ b/src/style/animations/fade-enter.jsx
@@ -1,6 +1,6 @@
import { css } from "styled-components";
-export const fadeEnter = timing => css`
+export const fadeEnter = (timing) => css`
&-appear {
opacity: 0;
}
diff --git a/src/assets/animations/fade-exit.jsx b/src/style/animations/fade-exit.jsx
similarity index 81%
rename from src/assets/animations/fade-exit.jsx
rename to src/style/animations/fade-exit.jsx
index a79b5e2c..94100be7 100644
--- a/src/assets/animations/fade-exit.jsx
+++ b/src/style/animations/fade-exit.jsx
@@ -1,6 +1,6 @@
import { css } from "styled-components";
-export const fadeExit = timing => css`
+export const fadeExit = (timing) => css`
&-exit {
opacity: 1;
}
diff --git a/src/assets/animations/index.js b/src/style/animations/index.js
similarity index 100%
rename from src/assets/animations/index.js
rename to src/style/animations/index.js
diff --git a/src/assets/animations/spin.jsx b/src/style/animations/spin.jsx
similarity index 100%
rename from src/assets/animations/spin.jsx
rename to src/style/animations/spin.jsx
diff --git a/src/themes/colors.js b/src/style/color/colors.js
similarity index 80%
rename from src/themes/colors.js
rename to src/style/color/colors.js
index 89f4a5e8..e9322774 100644
--- a/src/themes/colors.js
+++ b/src/style/color/colors.js
@@ -1,10 +1,11 @@
-export const colors = {
+const colors = {
whiteSimple: "#fff",
whiteGray: "#f9f9f9",
whiteGrayLight: "#f7f8f9",
whiteGrayDark: "#f2f2f2",
whiteBlue: "#f2f6fa",
whiteYellow: "#f8f8ee",
+
/* for types of records */
navyBlue: "#1e8ab9",
darkBlue: "#1A5D7B",
@@ -13,13 +14,16 @@ export const colors = {
greenish: "#3f9655",
lightYellow: "#ffc228",
lightRed: "#f65502",
+
+ slate: "#3b4b5a",
gray: "#5d7784",
- graySimpleLight: "#ddd",
- graySimple: "#bbb",
- grayWhite: "#aaa",
+ grayMedium: "#dadada",
+ graySimple: "#bbbbbb",
+ graySimpleLight: "#dddddd",
+ grayWhite: "#aaaaaa",
grayMiddle: "#6a6e71",
+ grayDark: "#A8B3BE",
grayLight: "#f3f3f3",
- slate: "#3b4b5a",
grayBlueLight: "#6b7d86",
grayBlueLight2: "#e2e5e8",
grayBlueMiddle: "#bec9ce",
@@ -27,33 +31,33 @@ export const colors = {
grayBlueDarkLight: "#798990",
grayBlueDark: "#687d90",
grayBlueDark2: "#ebf0f2",
- grayRedLight: "#eee",
+ grayRedLight: "#eeeeee",
grayRedDark: "#dedede",
- grayRedMiddle: "#ccc",
+ grayRedMiddle: "#cccccc",
+
brownSimple: "#AC3D03",
- black: "#000",
+ simpleBlue: "#4c6a95",
+ middleBlue: "#42608C",
blueWhite: "#e4e9eb",
blueGray: "#6a8694",
- brownGray: "#aaaaaa",
veryLightBlue: "#e4e9ec",
-
darkestBlue: "#2e4669",
- middleBlue: "#42608C",
- simpleBlue: "#4c6a95",
iceBlue: "#f0f1f2",
darkestPink: "#b6245e",
middlePink: "#be2c66",
simplePink: "#d2477e",
- darkBlack: "#222",
bloodOrange: "#ef5305",
orangeSimple: "#f89406",
orangeDark: "#fabf29",
+ black: "#000000",
+ darkBlack: "#222222",
+
red: "#dc0909",
green: "#008000",
};
-export default colors;
+export { colors };
diff --git a/src/themes/index.js b/src/style/color/index.js
similarity index 100%
rename from src/themes/index.js
rename to src/style/color/index.js
diff --git a/src/resources/styles/fonts.css b/src/style/font/fonts.css
similarity index 62%
rename from src/resources/styles/fonts.css
rename to src/style/font/fonts.css
index bd021549..26ba7cfd 100644
--- a/src/resources/styles/fonts.css
+++ b/src/style/font/fonts.css
@@ -1,27 +1,27 @@
@font-face {
font-family: "Open Sans";
- src: url(../fonts/open-sans/OpenSans-Regular.ttf);
+ src: url(./fonts/open-sans/OpenSans-Regular.ttf);
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: "Open Sans";
- src: url(../fonts/open-sans/OpenSans-Light.ttf);
+ src: url(./fonts/open-sans/OpenSans-Light.ttf);
font-weight: 300;
font-style: normal;
}
@font-face {
font-family: "Open Sans";
- src: url(../fonts/open-sans/OpenSans-SemiBold.ttf);
+ src: url(./fonts/open-sans/OpenSans-SemiBold.ttf);
font-weight: 600;
font-style: normal;
}
@font-face {
font-family: "Open Sans";
- src: url(../fonts/open-sans/OpenSans-Bold.ttf);
+ src: url(./fonts/open-sans/OpenSans-Bold.ttf);
font-weight: 700;
font-style: normal;
}
diff --git a/src/resources/fonts/open-sans/LICENSE.txt b/src/style/font/fonts/open-sans/LICENSE.txt
similarity index 100%
rename from src/resources/fonts/open-sans/LICENSE.txt
rename to src/style/font/fonts/open-sans/LICENSE.txt
diff --git a/src/resources/fonts/open-sans/OpenSans-Bold.ttf b/src/style/font/fonts/open-sans/OpenSans-Bold.ttf
similarity index 100%
rename from src/resources/fonts/open-sans/OpenSans-Bold.ttf
rename to src/style/font/fonts/open-sans/OpenSans-Bold.ttf
diff --git a/src/resources/fonts/open-sans/OpenSans-BoldItalic.ttf b/src/style/font/fonts/open-sans/OpenSans-BoldItalic.ttf
similarity index 100%
rename from src/resources/fonts/open-sans/OpenSans-BoldItalic.ttf
rename to src/style/font/fonts/open-sans/OpenSans-BoldItalic.ttf
diff --git a/src/resources/fonts/open-sans/OpenSans-ExtraBold.ttf b/src/style/font/fonts/open-sans/OpenSans-ExtraBold.ttf
similarity index 100%
rename from src/resources/fonts/open-sans/OpenSans-ExtraBold.ttf
rename to src/style/font/fonts/open-sans/OpenSans-ExtraBold.ttf
diff --git a/src/resources/fonts/open-sans/OpenSans-ExtraBoldItalic.ttf b/src/style/font/fonts/open-sans/OpenSans-ExtraBoldItalic.ttf
similarity index 100%
rename from src/resources/fonts/open-sans/OpenSans-ExtraBoldItalic.ttf
rename to src/style/font/fonts/open-sans/OpenSans-ExtraBoldItalic.ttf
diff --git a/src/resources/fonts/open-sans/OpenSans-Italic.ttf b/src/style/font/fonts/open-sans/OpenSans-Italic.ttf
similarity index 100%
rename from src/resources/fonts/open-sans/OpenSans-Italic.ttf
rename to src/style/font/fonts/open-sans/OpenSans-Italic.ttf
diff --git a/src/resources/fonts/open-sans/OpenSans-Light.ttf b/src/style/font/fonts/open-sans/OpenSans-Light.ttf
similarity index 100%
rename from src/resources/fonts/open-sans/OpenSans-Light.ttf
rename to src/style/font/fonts/open-sans/OpenSans-Light.ttf
diff --git a/src/resources/fonts/open-sans/OpenSans-LightItalic.ttf b/src/style/font/fonts/open-sans/OpenSans-LightItalic.ttf
similarity index 100%
rename from src/resources/fonts/open-sans/OpenSans-LightItalic.ttf
rename to src/style/font/fonts/open-sans/OpenSans-LightItalic.ttf
diff --git a/src/resources/fonts/open-sans/OpenSans-Regular.ttf b/src/style/font/fonts/open-sans/OpenSans-Regular.ttf
similarity index 100%
rename from src/resources/fonts/open-sans/OpenSans-Regular.ttf
rename to src/style/font/fonts/open-sans/OpenSans-Regular.ttf
diff --git a/src/resources/fonts/open-sans/OpenSans-SemiBold.ttf b/src/style/font/fonts/open-sans/OpenSans-SemiBold.ttf
similarity index 100%
rename from src/resources/fonts/open-sans/OpenSans-SemiBold.ttf
rename to src/style/font/fonts/open-sans/OpenSans-SemiBold.ttf
diff --git a/src/resources/fonts/open-sans/OpenSans-SemiBoldItalic.ttf b/src/style/font/fonts/open-sans/OpenSans-SemiBoldItalic.ttf
similarity index 100%
rename from src/resources/fonts/open-sans/OpenSans-SemiBoldItalic.ttf
rename to src/style/font/fonts/open-sans/OpenSans-SemiBoldItalic.ttf
diff --git a/src/style/font/index.js b/src/style/font/index.js
new file mode 100644
index 00000000..836d9637
--- /dev/null
+++ b/src/style/font/index.js
@@ -0,0 +1 @@
+export { default as fonts } from "./fonts.css";
diff --git a/src/style/index.js b/src/style/index.js
new file mode 100644
index 00000000..66419133
--- /dev/null
+++ b/src/style/index.js
@@ -0,0 +1,3 @@
+export * from "./animations";
+export * from "./color";
+export * from "./font";
diff --git a/src/utils/cookie-service.js b/src/utils/cookie-service.js
deleted file mode 100644
index 17bd9aaa..00000000
--- a/src/utils/cookie-service.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import { cookie } from "browser-cookie-lite";
-
-import { SECURE } from "./storage";
-
-export function removeCookie({ name, path, domain, secure = SECURE }) {
- return cookie(name, "", -1, path, domain, secure);
-}
-
-export function saveCookie({
- name,
- value,
- ttl,
- path,
- domain,
- secure = SECURE,
-}) {
- return cookie(name, JSON.stringify(value), ttl, path, domain, secure);
-}
-
-export function getCookie(name) {
- return JSON.parse(cookie(name) || "{}");
-}
diff --git a/src/utils/helpers.js b/src/utils/helpers.js
index 11defbf7..a648a7ee 100644
--- a/src/utils/helpers.js
+++ b/src/utils/helpers.js
@@ -1,20 +1,10 @@
import dayjs from "dayjs";
import matchSorter from "match-sorter";
-import { isEqual } from "lodash-es";
+import { isEqual, get } from "lodash-es";
+import jump from "jump.js";
-const DATE_TIME_FORMAT_SLASHES = "DD/MM/YY HH:mm";
const DATE_TIME_FORMAT_SIMPLE = "D MMM YYYY, HH:mm:ss";
-const defaultConfValues = {
- new: false,
- reinit: false,
- exact: false,
- ha: false,
- junk: false,
- nm: false,
- det: false,
-};
-
export function createUUID() {
// http://www.ietf.org/rfc/rfc4122.txt
var s = [];
@@ -30,38 +20,15 @@ export function createUUID() {
return uuid;
}
-export function dataURItoBlob(dataURI) {
- // convert base64/URLEncoded data component to raw binary data held in a string
- var byteString;
- if (dataURI.split(",")[0].indexOf("base64") >= 0)
- byteString = atob(dataURI.split(",")[1]);
- else byteString = unescape(dataURI.split(",")[1]);
- // separate out the mime component
- var mimeString = dataURI
- .split(",")[0]
- .split(":")[1]
- .split(";")[0];
- // write the bytes of the string to a typed array
- var ia = new Uint8Array(byteString.length);
- for (var i = 0; i < byteString.length; i++) {
- ia[i] = byteString.charCodeAt(i);
- }
- return new Blob([ia], { type: mimeString });
-}
-
-export function timeFormatSlashes(time) {
- return timeFormater(time, DATE_TIME_FORMAT_SLASHES);
-}
-
-export function timeFormat(time) {
- return timeFormater(time, DATE_TIME_FORMAT_SIMPLE);
-}
-
+// DATA FORMATTERS
function timeFormater(time, fromat) {
const formatedTime =
time && dayjs(time).isValid() && dayjs(time).format(fromat);
return formatedTime || "β";
}
+export function timeFormat(time) {
+ return timeFormater(time, DATE_TIME_FORMAT_SIMPLE);
+}
export function formatDate(date, format = "DD MMM YYYY, HH:mm:ss") {
if (!dayjs(date).isValid()) return null;
@@ -69,14 +36,6 @@ export function formatDate(date, format = "DD MMM YYYY, HH:mm:ss") {
return dayjs(date).format(format);
}
-/**
- *
- * ΠΏΡΠΎΠ²Π΅ΡΠΈΡΡ ΠΈ ΠΏΠΎΠΌΠ΅Π½ΡΡΡ Π½Π°Π·Π²Π°Π½ΠΈΠ΅ ΠΏΡΠΈ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠΈ Π΄Π°Π½Π½ΠΎΠ³ΠΎ Ρ
Π΅Π»ΠΏΠ΅ΡΠ°
- */
-// export function formatDate(date) {
-// return date.toISOString();
-// }
-
export function dateToIso(date) {
return date.toISOString();
}
@@ -90,9 +49,18 @@ export function formatSex(sex) {
);
}
+export function formatFaceSize(facesize) {
+ if (facesize !== 0 && !facesize) return null;
+
+ let fs = Math.floor(facesize / 1000);
+
+ return fs >= 100 ? (fs = "99k+") : fs + "k";
+}
+
+// ARRAY
export function toggleInArray(arr = [], item) {
return arr.includes(item)
- ? arr.filter(disabled => item !== disabled)
+ ? arr.filter((disabled) => item !== disabled)
: arr.concat(item);
}
@@ -112,40 +80,6 @@ export function searchInList(list, query, keys) {
: list;
}
-export function formatFaceSize(facesize) {
- if (facesize !== 0 && !facesize) return null;
-
- let fs = Math.floor(facesize / 1000);
-
- return fs >= 100 ? (fs = "99k+") : fs + "k";
-}
-
-export function filtersToString(filters) {
- return Object.keys(filters).join();
-}
-
-export function parseFilters(filters) {
- if (filters && filters.conf) {
- const confArray = filters.conf.split(",");
- const confObj = confArray.reduce((obj, value) => {
- obj[value] = true;
- return obj;
- }, {});
-
- const mappedFilters = {
- offset: Number(filters.offset || 0),
- limit: Number(filters.limit),
- };
-
- return {
- ...filters,
- ...mappedFilters,
- conf: { ...defaultConfValues, ...confObj },
- };
- }
- return {};
-}
-
export function isNotEmpty(value, isZeroEmpty) {
return (
typeof value !== "undefined" &&
@@ -155,44 +89,20 @@ export function isNotEmpty(value, isZeroEmpty) {
);
}
-export function getBase64(file) {
- return new Promise((resolve, reject) => {
- const reader = new FileReader();
- reader.readAsDataURL(file);
- reader.onload = () => resolve(reader.result);
- reader.onerror = error => reject(error);
- });
-}
-
export function findOptionByValue(options, value) {
- return options.find(option => isEqual(option.value, value));
-}
-
-export function transformFilters(filters) {
- const confString =
- filters.conf &&
- Object.keys(filters.conf)
- .filter(prop => filters.conf[prop] === true)
- .join();
- return { ...filters, conf: confString };
+ return options.find((option) => isEqual(option.value, value));
}
export function mapDataToGetParams(data) {
return (
"?" +
Object.keys(data)
- .filter(key => data[key] !== null)
- .map(key => `${key}=${data[key]}`)
+ .filter((key) => data[key] !== null)
+ .map((key) => `${key}=${data[key]}`)
.join("&")
);
}
-export function mapFiltersToGetParams(filters) {
- const transformedFilters = transformFilters(filters);
-
- return mapDataToGetParams(transformedFilters);
-}
-
export function getStringShort(
string,
maxLength = 10,
@@ -212,6 +122,22 @@ export function getStringShort(
)}...${string.slice(-(lastPartCount || defaultPartCount), stringLength)}`;
}
+export function getFilenameShort(filename, maxLength = 10) {
+ if (filename.length < maxLength) {
+ return filename;
+ }
+
+ const extention = filename.split(".").pop();
+
+ const firstPart = filename.slice(0, 4);
+ const lastPart = filename.slice(
+ -extention.length - 4,
+ filename.length - extention.length - 1
+ );
+
+ return `${firstPart}...${lastPart}.${extention}`;
+}
+
export function capitalize(s) {
if (typeof s !== "string") return "";
@@ -222,6 +148,7 @@ export function hasProperty(obj, property) {
return Object.prototype.hasOwnProperty.call(obj, property);
}
+// DATE/TIME
export function isSameDate(d1, d2) {
if (!d1 || !d2) return false;
@@ -241,3 +168,70 @@ export function isValidDate(date, format) {
return dayjs(date).isValid();
}
+
+export function getFromMapByIds(map, ids) {
+ return ids.map((id) => map[id]).filter(Boolean);
+}
+
+export function removeKeyFromMap(map, key) {
+ // eslint-disable-next-line no-unused-vars
+ const { [key]: removed, ...restMap } = map;
+
+ return restMap;
+}
+
+export function scrollToTop(options = {}) {
+ document.querySelector("#app-container").scrollIntoView({
+ behavior: "smooth",
+ ...options,
+ });
+}
+
+export function scrollToItem(selector, itemHeight, onScroll, options = {}) {
+ const node = document.querySelector(selector);
+
+ if (node) {
+ // 200 ΡΠ΅ΠΊΡΠ½Π΄ - ΡΠ°ΠΉΠΌΠΈΠ½Π³ Π°Π½ΠΈΠΌΠ°ΡΠΈΡ ΡΡΠΉΠ΄Π° ΠΏΡΠΈ ΠΏΠ΅ΡΠ΅Ρ
ΠΎΠ΄Π΅ ΠΌΠ΅ΠΆΠ΄Ρ ΡΡΡΠ°Π½ΠΈΡΠ°ΠΌΠΈ
+ setTimeout(() => {
+ jump(node, {
+ offset: -(window.innerHeight / 2) + itemHeight,
+ duration: 500,
+ callback: onScroll,
+ ...options,
+ });
+ }, 200);
+ }
+}
+
+export function processFormValues(values) {
+ return Object.entries(values).reduce((values, [key, value]) => {
+ if (value && value.value !== undefined) {
+ return { ...values, [key]: value.value };
+ } else if (value instanceof Array) {
+ const mapValue = value.map((value) => get(value, "value", value));
+
+ return { ...values, [key]: mapValue };
+ } else {
+ return { ...values, [key]: value };
+ }
+ }, {});
+}
+
+export function isNumeric(x) {
+ return (typeof x === "number" || typeof x === "string") && !isNaN(Number(x));
+}
+
+export function setNativeValue(element, value) {
+ const valueSetter = Object.getOwnPropertyDescriptor(element, "value").set;
+ const prototype = Object.getPrototypeOf(element);
+ const prototypeValueSetter = Object.getOwnPropertyDescriptor(
+ prototype,
+ "value"
+ ).set;
+
+ if (valueSetter && valueSetter !== prototypeValueSetter) {
+ prototypeValueSetter.call(element, value);
+ } else {
+ valueSetter.call(element, value);
+ }
+}
diff --git a/src/utils/index.js b/src/utils/index.js
index c516aaf3..d4e09d7b 100644
--- a/src/utils/index.js
+++ b/src/utils/index.js
@@ -1,3 +1 @@
-export * from "./cookie-service";
export * from "./helpers";
-export * from "./storage";
diff --git a/src/utils/storage.js b/src/utils/storage.js
deleted file mode 100644
index 457e1968..00000000
--- a/src/utils/storage.js
+++ /dev/null
@@ -1,52 +0,0 @@
-import { cookie } from "browser-cookie-lite";
-
-let storage = window.localStorage;
-
-const NAME = "localStorageFallback";
-const TTL = 365 * 24 * 60 * 60;
-const PATH = "/";
-const DOMAIN = null;
-
-export const SECURE = window.location.protocol.startsWith("https");
-
-try {
- if (!window.localStorage) {
- throw new Error("localStorage not supported");
- }
- const keyProbe = "___test-local-stoage___";
- storage.setItem(keyProbe, keyProbe);
- storage.removeItem(keyProbe);
-} catch (e) {
- storage = {
- getItem: key => {
- const data = get();
- return data[key];
- },
-
- setItem: (key, item) => {
- const data = get();
- data[key] = item.toString();
- save(data);
- },
-
- removeItem: key => {
- const data = get();
- delete data[key];
- save(data);
- },
-
- clear: () => {
- cookie(NAME, "", -1);
- },
- };
-}
-
-function get() {
- return JSON.parse(cookie(NAME) || "{}");
-}
-
-function save(data) {
- cookie(NAME, JSON.stringify(data), TTL, PATH, DOMAIN, SECURE);
-}
-
-export default storage;
diff --git a/test/__mocks__/react-i18next.js b/test/__mocks__/react-i18next.js
new file mode 100644
index 00000000..35a1ca45
--- /dev/null
+++ b/test/__mocks__/react-i18next.js
@@ -0,0 +1,58 @@
+// https://github.com/i18next/react-i18next/tree/master/example/test-jest
+const React = require("react");
+const reactI18next = require("react-i18next");
+
+const hasChildren = (node) =>
+ node && (node.children || (node.props && node.props.children));
+
+const getChildren = (node) =>
+ node && node.children ? node.children : node.props && node.props.children;
+
+const renderNodes = (reactNodes) => {
+ if (typeof reactNodes === "string") {
+ return reactNodes;
+ }
+
+ return Object.keys(reactNodes).map((key, i) => {
+ const child = reactNodes[key];
+ const isElement = React.isValidElement(child);
+
+ if (typeof child === "string") {
+ return child;
+ }
+ if (hasChildren(child)) {
+ const inner = renderNodes(getChildren(child));
+ return React.cloneElement(child, { ...child.props, key: i }, inner);
+ }
+ if (typeof child === "object" && !isElement) {
+ return Object.keys(child).reduce(
+ (str, childKey) => `${str}${child[childKey]}`,
+ ""
+ );
+ }
+
+ return child;
+ });
+};
+
+const useMock = [(k) => k, {}];
+useMock.t = (k) => k;
+useMock.i18n = { addResourceBundle: () => {} };
+
+module.exports = {
+ // this mock makes sure any components using the translate HoC receive the t function as a prop
+ withTranslation: () => (Component) => (props) => (
+ k} {...props} />
+ ),
+ Trans: ({ children }) => renderNodes(children),
+ Translation: ({ children }) => children((k) => k, { i18n: {} }),
+ useTranslation: () => useMock,
+
+ // mock if needed
+ I18nextProvider: reactI18next.I18nextProvider,
+ initReactI18next: reactI18next.initReactI18next,
+ setDefaults: reactI18next.setDefaults,
+ getDefaults: reactI18next.getDefaults,
+ setI18n: reactI18next.setI18n,
+ getI18n: reactI18next.getI18n,
+};
diff --git a/test/generate.js b/test/generate.js
index b806871f..97b553e0 100644
--- a/test/generate.js
+++ b/test/generate.js
@@ -18,7 +18,7 @@ function getRandomInt(min, max) {
}
function getRandomName() {
- return sequence(x => fake(f => f.hacker.verb() + `_${x}`));
+ return sequence((x) => fake((f) => f.hacker.verb() + `_${x}`));
}
const licenseTypes = ["basic", "standard", "standard+", "advanced"];
@@ -52,7 +52,7 @@ const sourceBuilder = build("Source")
manual_create_liveness_only: bool(),
manual_check_liveness: bool(),
})
- .map(source => ({
+ .map((source) => ({
...source,
store_images_for_confs: uniq(source.store_images_for_confs),
}));
@@ -78,8 +78,8 @@ const sourceStatsBuilder = build("Source Stats").fields({
});
const userBuilder = build("User").fields({
- username: fake(f => f.internet.userName()),
- password: fake(f => f.internet.password()),
+ username: fake((f) => f.internet.userName()),
+ password: fake((f) => f.internet.password()),
});
function generateSources(count) {
diff --git a/webpack.config.js b/webpack.config.js
deleted file mode 100644
index 27aa49fd..00000000
--- a/webpack.config.js
+++ /dev/null
@@ -1,128 +0,0 @@
-const path = require("path");
-const webpack = require("webpack");
-
-const { CleanWebpackPlugin } = require("clean-webpack-plugin");
-
-const MiniCssExtractPlugin = require("mini-css-extract-plugin");
-const OptimizeCssAssetsPlugin = require("optimize-css-assets-webpack-plugin");
-
-// --------
-// environment variables
-const { NODE_ENV, npm_package_version: VERSION } = process.env;
-
-console.group("Build info");
-console.log(`Release version is ${VERSION}`);
-console.log(`Webpack version is ${webpack.version}`);
-console.log(`Webpack running in ${NODE_ENV} environment`);
-console.groupEnd();
-
-// app paths
-const PATHS = {
- src: path.resolve(__dirname, "src"),
- build: path.resolve(__dirname, "dist"),
- node_modules: path.resolve(__dirname, "node_modules"),
-};
-// --------
-
-const config = {
- context: PATHS.src,
-
- mode: NODE_ENV,
-
- devtool: "hidden-source-map",
-
- entry: {
- client: [path.join(PATHS.src, "index.js")],
- },
-
- output: {
- path: PATHS.build,
- publicPath: "/",
- filename: "index.js",
- },
-
- resolve: {
- modules: [PATHS.src, "node_modules"],
- extensions: [".js", ".jsx"],
- },
-
- module: {
- rules: [
- {
- test: /\.jsx?$/i,
- exclude: /node_modules/,
- use: [
- {
- loader: "babel-loader",
- },
- ],
- },
-
- {
- test: /\.css$/,
- use: [
- { loader: MiniCssExtractPlugin.loader },
- { loader: "css-loader" },
- ],
- },
-
- {
- test: /\.(jpg|jpe?g|png|gif)$/,
- use: [
- {
- loader: "url-loader",
- options: {
- limit: 4000,
- name: "images/[name].[ext]",
- },
- },
- "image-webpack-loader",
- ],
- },
-
- {
- test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
- use: [
- {
- loader: "url-loader",
- options: {
- mimetype: "image/svg+xml",
- name: "images/[name].[ext]",
- },
- },
- ],
- },
-
- {
- test: /\.(ttf|eot|woff|woff2)$/,
- use: {
- loader: "file-loader",
- options: {
- name: "fonts/[name].[ext]",
- },
- },
- },
- ],
- },
-
- plugins: [
- new webpack.DefinePlugin({
- "process.env": {
- VERSION: JSON.stringify(VERSION),
- NODE_ENV: JSON.stringify(NODE_ENV),
- },
- }),
-
- new CleanWebpackPlugin(),
-
- new MiniCssExtractPlugin({
- filename: `styles/main.css`,
- }),
-
- new OptimizeCssAssetsPlugin({
- assetNameRegExp: /\.css$/g,
- }),
- ],
-};
-
-module.exports = config;