github.com/minio/console@v1.4.1/web-app/src/screens/Console/Common/FormComponents/CSVMultiSelector/CSVMultiSelector.tsx (about)

     1  // This file is part of MinIO Console Server
     2  // Copyright (c) 2021 MinIO, Inc.
     3  //
     4  // This program is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Affero General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // This program is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12  // GNU Affero General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Affero General Public License
    15  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16  import React, {
    17    ChangeEvent,
    18    createRef,
    19    useCallback,
    20    useEffect,
    21    useRef,
    22    useState,
    23    Fragment,
    24  } from "react";
    25  import get from "lodash/get";
    26  import { AddIcon, Box, HelpIcon, InputBox, InputLabel, Tooltip } from "mds";
    27  
    28  interface ICSVMultiSelector {
    29    elements: string;
    30    name: string;
    31    label: string;
    32    tooltip?: string;
    33    commonPlaceholder?: string;
    34    withBorder?: boolean;
    35    onChange: (elements: string) => void;
    36  }
    37  
    38  const CSVMultiSelector = ({
    39    elements,
    40    name,
    41    label,
    42    tooltip = "",
    43    commonPlaceholder = "",
    44    onChange,
    45    withBorder = false,
    46  }: ICSVMultiSelector) => {
    47    const [currentElements, setCurrentElements] = useState<string[]>([""]);
    48    const bottomList = createRef<HTMLDivElement>();
    49  
    50    // Use effect to get the initial values from props
    51    useEffect(() => {
    52      if (
    53        currentElements.length === 1 &&
    54        currentElements[0] === "" &&
    55        elements &&
    56        elements !== ""
    57      ) {
    58        const elementsSplit = elements.split(",");
    59        elementsSplit.push("");
    60  
    61        setCurrentElements(elementsSplit);
    62      }
    63  
    64      // eslint-disable-next-line react-hooks/exhaustive-deps
    65    }, [elements, currentElements]);
    66  
    67    // Use effect to send new values to onChange
    68    useEffect(() => {
    69      if (currentElements.length > 1) {
    70        const refScroll = bottomList.current;
    71        if (refScroll) {
    72          refScroll.scrollIntoView(false);
    73        }
    74      }
    75    }, [currentElements, bottomList]);
    76  
    77    const onChangeCallback = useCallback(
    78      (newString: string) => {
    79        onChange(newString);
    80      },
    81      [onChange],
    82    );
    83  
    84    // We avoid multiple re-renders / hang issue typing too fast
    85    const firstUpdate = useRef(true);
    86    useEffect(() => {
    87      if (firstUpdate.current) {
    88        firstUpdate.current = false;
    89        return;
    90      }
    91      const elementsString = currentElements
    92        .filter((element) => element.trim() !== "")
    93        .join(",");
    94  
    95      onChangeCallback(elementsString);
    96  
    97      // eslint-disable-next-line react-hooks/exhaustive-deps
    98    }, [currentElements]);
    99  
   100    // If the last input is not empty, we add a new one
   101    const addEmptyLine = (elementsUp: string[]) => {
   102      if (elementsUp[elementsUp.length - 1].trim() !== "") {
   103        const cpList = [...elementsUp];
   104        cpList.push("");
   105        setCurrentElements(cpList);
   106      }
   107    };
   108  
   109    // Onchange function for input box, we get the dataset-index & only update that value in the array
   110    const onChangeElement = (e: ChangeEvent<HTMLInputElement>) => {
   111      e.persist();
   112  
   113      let updatedElement = [...currentElements];
   114      const index = get(e.target, "dataset.index", "0");
   115      const indexNum = parseInt(index);
   116      updatedElement[indexNum] = e.target.value;
   117  
   118      setCurrentElements(updatedElement);
   119    };
   120  
   121    const inputs = currentElements.map((element, index) => {
   122      return (
   123        <InputBox
   124          key={`csv-multi-${name}-${index.toString()}`}
   125          id={`${name}-${index.toString()}`}
   126          label={""}
   127          name={`${name}-${index.toString()}`}
   128          value={currentElements[index]}
   129          onChange={onChangeElement}
   130          index={index}
   131          placeholder={commonPlaceholder}
   132          overlayIcon={index === currentElements.length - 1 ? <AddIcon /> : null}
   133          overlayAction={() => {
   134            addEmptyLine(currentElements);
   135          }}
   136        />
   137      );
   138    });
   139  
   140    return (
   141      <Fragment>
   142        <Box sx={{ display: "flex" }} className={"inputItem"}>
   143          <InputLabel
   144            sx={{
   145              alignItems: "flex-start",
   146            }}
   147          >
   148            <span>{label}</span>
   149            {tooltip !== "" && (
   150              <Box
   151                sx={{
   152                  marginLeft: 5,
   153                  display: "flex",
   154                  alignItems: "center",
   155                  "& .min-icon": {
   156                    width: 13,
   157                  },
   158                }}
   159              >
   160                <Tooltip tooltip={tooltip} placement="top">
   161                  <Box className={tooltip}>
   162                    <HelpIcon />
   163                  </Box>
   164                </Tooltip>
   165              </Box>
   166            )}
   167          </InputLabel>
   168          <Box
   169            withBorders={withBorder}
   170            sx={{
   171              width: "100%",
   172              overflowY: "auto",
   173              height: 150,
   174              position: "relative",
   175            }}
   176          >
   177            {inputs}
   178            <div ref={bottomList} />
   179          </Box>
   180        </Box>
   181      </Fragment>
   182    );
   183  };
   184  export default CSVMultiSelector;