diff --git a/src/components/util/MultiSelect.tsx b/src/components/util/MultiSelect.tsx
new file mode 100644
index 00000000..7c527ee2
--- /dev/null
+++ b/src/components/util/MultiSelect.tsx
@@ -0,0 +1,169 @@
+/** ****************************************************************************
+ * Copyright 2020-2020 Exactpro (Exactpro Systems Limited)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ ***************************************************************************** */
+
+import React, { useState, useRef, useEffect } from 'react';
+import '../../styles/multiselect.scss';
+import { ModalPortal } from './Portal';
+import { raf } from '../../helpers/raf';
+import { useOutsideClickListener } from '../../hooks';
+
+interface Props
{
+ className?: string;
+ options: Array;
+ onChange: (selected: Array) => void;
+ selected?: Array;
+}
+
+function MultiSelect({ options, onChange, selected = [], className = '' }: Props) {
+ const [selectedValues, setSelectedValues] = useState(selected || []);
+ const [isOpen, setIsOpen] = useState(false);
+ const [isAllSelected, setIsAllSelected] = useState(false);
+ const [partialSelect, setPartialSelect] = useState(0);
+ const dropdownRef = useRef(null);
+ const multiselectBodyRef = useRef(null);
+ const indeterminateCheckboxRef = useRef(null);
+
+ useEffect(() => {
+ if (selectedValues.length === 0) {
+ setIsAllSelected(false);
+ setPartialSelect(0);
+ } else if (selectedValues.length === options.length) {
+ setIsAllSelected(true);
+ setPartialSelect(2);
+ } else {
+ setIsAllSelected(false);
+ setPartialSelect(1);
+ }
+ }, [selectedValues, options.length]);
+
+ useEffect(() => {
+ if (indeterminateCheckboxRef.current) {
+ indeterminateCheckboxRef.current.indeterminate = partialSelect === 1;
+ }
+ }, [partialSelect]);
+
+ const handleSelectAll = () => {
+ if (!isAllSelected) {
+ setSelectedValues([...options]);
+ } else {
+ setSelectedValues([]);
+ }
+ };
+
+ const handlePartialSelect = () => {
+ if (partialSelect === 0 || partialSelect === 1) {
+ setSelectedValues([...options]);
+ } else {
+ setSelectedValues([]);
+ }
+ };
+
+ const handleSelect = (value: T) => {
+ const newSelectedValues = selectedValues.includes(value)
+ ? selectedValues.filter(v => v !== value)
+ : [...selectedValues, value];
+
+ setSelectedValues(newSelectedValues);
+ if (onChange) {
+ onChange(newSelectedValues);
+ }
+ };
+
+ const toggleDropdown = () => setIsOpen(!isOpen);
+
+ React.useLayoutEffect(() => {
+ if (isOpen) {
+ raf(() => {
+ if (multiselectBodyRef.current && dropdownRef.current) {
+ const { left, bottom } = dropdownRef.current.getBoundingClientRect();
+ const clientWidth = document.documentElement.clientWidth;
+
+ let calculatedWidth = multiselectBodyRef.current.clientWidth;
+ const leftPosition = left;
+
+ if (left + calculatedWidth > clientWidth) {
+ calculatedWidth = clientWidth - left - 10;
+ }
+
+ multiselectBodyRef.current.style.left = `${leftPosition}px`;
+ multiselectBodyRef.current.style.top = `${bottom}px`;
+ multiselectBodyRef.current.style.width = `${calculatedWidth}px`;
+ }
+ }, 2);
+ }
+ }, [isOpen]);
+
+ useOutsideClickListener(
+ multiselectBodyRef,
+ (e: MouseEvent) => {
+ if (
+ e.target instanceof Element &&
+ !multiselectBodyRef.current?.contains(e.target) &&
+ !dropdownRef.current?.contains(e.target)
+ ) {
+ setIsOpen(false);
+ }
+ },
+ isOpen,
+ );
+
+ return (
+
+
+
+ {selectedValues.length > 0 ? selectedValues.join(', ') : ''}
+
+
+
+
+
+
+ );
+}
+
+export default MultiSelect;
diff --git a/src/styles/multiselect.scss b/src/styles/multiselect.scss
new file mode 100644
index 00000000..32113230
--- /dev/null
+++ b/src/styles/multiselect.scss
@@ -0,0 +1,107 @@
+/******************************************************************************
+ * Copyright 2020-2020 Exactpro (Exactpro Systems Limited)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ ******************************************************************************/
+
+@import './common/vars';
+
+.multiselect {
+ border: 1px solid #ccc;
+ padding: 0;
+ border-radius: 4px;
+ position: relative;
+ cursor: pointer;
+ user-select: none;
+ color: $primaryTextColor;
+ font-size: 14px;
+}
+
+.dropdown-header {
+ border-bottom: 1px solid #eee;
+ background: {
+ image: url(../../resources/icons/arr1-down.svg);
+ color: transparent;
+ repeat: no-repeat;
+ size: 15px;
+ position-x: 95%;
+ position-y: 50%;
+ }
+ width: 150px;
+ height: 24px;
+ display: flex;
+ justify-content: flex-start;
+ align-items: center;
+ padding-left: 4px;
+
+ &__text {
+ width: 130px;
+ height: 100%;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ }
+}
+
+.dropdown-menu {
+ position: absolute;
+ top: 102%;
+ left: 0;
+ right: 0;
+ z-index: 1000;
+ padding: 5px;
+ background: white;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ max-height: 400px;
+ min-width: min-content;
+ width: fit-content;
+ max-width: 600px;
+ overflow-y: auto;
+ overflow-x: hidden;
+ @include scrollbar();
+}
+
+.dropdown-list {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+}
+
+.dropdown-item {
+ padding: 2px 5px 2px 2px;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ border-radius: 4px;
+ white-space: nowrap;
+}
+.dropdown-item:first-child {
+ margin-bottom: 5px;
+}
+
+.dropdown-item input[type='checkbox'] {
+ margin-right: 5px;
+}
+
+.dropdown-item.selected {
+ background-color: #f0f0f0;
+}
+
+.dropdown-item:hover {
+ background-color: #e9e9e9;
+}
+