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;