• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

CBIIT / crdc-datahub-ui / 18789341118

24 Oct 2025 06:57PM UTC coverage: 78.178% (+15.5%) from 62.703%
18789341118

push

github

web-flow
Merge pull request #888 from CBIIT/3.4.0

3.4.0 Release

4977 of 5488 branches covered (90.69%)

Branch coverage included in aggregate %.

8210 of 9264 new or added lines in 257 files covered. (88.62%)

6307 existing lines in 120 files now uncovered.

30203 of 39512 relevant lines covered (76.44%)

213.36 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

6.12
/src/components/Questionnaire/TableFileTypeAndExtensionInput.tsx
1
import {
1✔
2
  Autocomplete,
3
  TextField,
4
  TableCell,
5
  Tooltip,
6
  TooltipProps,
7
  styled,
8
  Paper,
9
} from "@mui/material";
10
import React, { FC, useEffect, useState, useRef } from "react";
1✔
11

12
import dropdownArrowsIcon from "../../assets/icons/dropdown_arrows.svg?url";
1✔
13
import { fileTypeExtensions } from "../../config/FileTypeConfig";
1✔
14
import useFormMode from "../../hooks/useFormMode";
1✔
15

16
const DropdownArrowsIcon = styled("div")(() => ({
1✔
NEW
17
  backgroundImage: `url("${dropdownArrowsIcon}")`,
×
UNCOV
18
  backgroundSize: "contain",
×
UNCOV
19
  backgroundRepeat: "no-repeat",
×
UNCOV
20
  width: "9.17px",
×
UNCOV
21
  height: "18px",
×
22
}));
1✔
23

24
type Props = {
25
  inputID: string;
26
  typeValue: string;
27
  extensionValue: string;
28
  options: string[];
29
  name: string;
30
  required?: boolean;
31
  helpText?: string;
32
  placeholder?: string;
33
  disableClearable?: boolean;
34
  onChange?: (e: React.SyntheticEvent, v: string, r: string) => void;
35
};
36

37
const StyledTooltip = styled((props: TooltipProps) => (
1✔
38
  <Tooltip classes={{ popper: props.className }} {...props} />
×
39
))(() => ({
1✔
UNCOV
40
  "& .MuiTooltip-tooltip": {
×
UNCOV
41
    marginTop: "4px !important",
×
UNCOV
42
    color: "#C93F08",
×
UNCOV
43
    background: "#FFFFFF",
×
UNCOV
44
    border: "1px solid #2B528B",
×
UNCOV
45
  },
×
UNCOV
46
  "& .MuiTooltip-arrow": {
×
UNCOV
47
    color: "#2B528B",
×
UNCOV
48
  },
×
49
}));
1✔
50

51
const StyledTableCell = styled(TableCell)(() => ({
1✔
UNCOV
52
  borderTop: "1px solid #6B7294 !important",
×
UNCOV
53
  borderRight: "1px solid #6B7294 !important",
×
UNCOV
54
  borderBottom: "none!important",
×
UNCOV
55
  borderLeft: "none!important",
×
UNCOV
56
  padding: "0",
×
UNCOV
57
  "& .MuiStack-root": {
×
UNCOV
58
    width: "auto",
×
UNCOV
59
  },
×
60
}));
1✔
61

62
const StyledPaper = styled(Paper)(() => ({
1✔
UNCOV
63
  borderRadius: "8px",
×
UNCOV
64
  border: "1px solid #6B7294",
×
UNCOV
65
  marginTop: "2px",
×
UNCOV
66
  "& .MuiAutocomplete-listbox": {
×
UNCOV
67
    padding: 0,
×
UNCOV
68
  },
×
UNCOV
69
  "& .MuiAutocomplete-option[aria-selected='true']": {
×
UNCOV
70
    color: "#083A50",
×
UNCOV
71
    background: "#FFFFFF",
×
UNCOV
72
  },
×
UNCOV
73
  "& .MuiAutocomplete-option": {
×
UNCOV
74
    padding: "0 10px",
×
UNCOV
75
    height: "35px",
×
UNCOV
76
    color: "#083A50",
×
UNCOV
77
    background: "#FFFFFF",
×
UNCOV
78
  },
×
UNCOV
79
  "& .MuiAutocomplete-option:hover": {
×
UNCOV
80
    backgroundColor: "#3E7E6D",
×
UNCOV
81
    color: "#FFFFFF",
×
UNCOV
82
  },
×
UNCOV
83
  "& .MuiAutocomplete-option.Mui-focused": {
×
UNCOV
84
    backgroundColor: "#3E7E6D !important",
×
UNCOV
85
    color: "#FFFFFF",
×
UNCOV
86
  },
×
87
}));
1✔
88

89
const StyledAutocomplete = styled(Autocomplete)(({ readOnly }: { readOnly?: boolean }) => ({
1✔
UNCOV
90
  "& .MuiInputBase-root": {
×
UNCOV
91
    backgroundColor: readOnly ? "#E5EEF4" : "#FFFFFF",
×
UNCOV
92
    "&.MuiAutocomplete-inputRoot.MuiInputBase-root": {
×
UNCOV
93
      display: "flex",
×
UNCOV
94
      alignItems: "center",
×
UNCOV
95
      padding: 0,
×
UNCOV
96
    },
×
UNCOV
97
    "& .MuiInputBase-input": {
×
UNCOV
98
      fontWeight: 400,
×
UNCOV
99
      fontSize: "16px",
×
UNCOV
100
      fontFamily: "'Nunito', 'Rubik', sans-serif",
×
UNCOV
101
      padding: "10px 12px 10px 12px !important",
×
UNCOV
102
      color: readOnly ? "#083A50" : "initial",
×
UNCOV
103
      cursor: readOnly ? "not-allowed !important" : "initial",
×
UNCOV
104
    },
×
UNCOV
105
    "& ::placeholder": {
×
UNCOV
106
      color: "#87878C",
×
UNCOV
107
      fontWeight: 400,
×
UNCOV
108
      opacity: 1,
×
UNCOV
109
    },
×
UNCOV
110
    "& .MuiAutocomplete-endAdornment": {
×
UNCOV
111
      right: "8px",
×
UNCOV
112
    },
×
UNCOV
113
    "&.Mui-focused": {
×
UNCOV
114
      boxShadow:
×
UNCOV
115
        "2px 2px 2px 1px rgba(38, 184, 147, 0.10), -2px -2px 2px 1px rgba(38, 184, 147, 0.20)",
×
UNCOV
116
    },
×
UNCOV
117
  },
×
118
}));
1✔
119

120
/**
121
 * Generates a generic autocomplete select box with a label and help text
122
 *
123
 * @param {Props} props
124
 * @returns {JSX.Element}
125
 */
126
const TableAutocompleteInput: FC<Props> = ({
1✔
UNCOV
127
  inputID,
×
UNCOV
128
  typeValue,
×
UNCOV
129
  extensionValue,
×
UNCOV
130
  name,
×
UNCOV
131
  required = false,
×
UNCOV
132
  helpText,
×
UNCOV
133
  onChange,
×
UNCOV
134
  ...rest
×
UNCOV
135
}) => {
×
136
  const { readOnlyInputs } = useFormMode();
×
137

138
  const [typeVal, setTypeVal] = useState(typeValue);
×
139
  const [extensionVal, setExtensionVal] = useState(extensionValue);
×
140
  const fileTypeRef = useRef<HTMLInputElement>(null);
×
141
  const fileExtensionRef = useRef<HTMLInputElement>(null);
×
142
  const [showFileTypeEror, setShowFileTypeError] = useState<boolean>(false);
×
143
  const [showFileExtensionError, setShowFileExtensionError] = useState<boolean>(false);
×
144

145
  useEffect(() => {
×
146
    const invalid = () => {
×
147
      setShowFileTypeError(true);
×
UNCOV
148
    };
×
149
    fileTypeRef.current?.addEventListener("invalid", invalid);
×
150
    return () => {
×
151
      fileTypeRef.current?.removeEventListener("invalid", invalid);
×
UNCOV
152
    };
×
UNCOV
153
  }, [fileTypeRef]);
×
154

155
  useEffect(() => {
×
156
    const invalid = () => {
×
157
      setShowFileExtensionError(true);
×
UNCOV
158
    };
×
159

160
    fileExtensionRef.current?.addEventListener("invalid", invalid);
×
161
    return () => {
×
162
      fileExtensionRef.current?.removeEventListener("invalid", invalid);
×
UNCOV
163
    };
×
UNCOV
164
  }, [fileExtensionRef]);
×
165

166
  const onTypeValChangeWrapper = (e, v, r) => {
×
167
    setShowFileTypeError(false);
×
168
    v = v || "";
×
169
    if (v === "") {
×
170
      fileTypeRef.current.setCustomValidity("Please specify a file type");
×
UNCOV
171
    } else {
×
172
      fileTypeRef.current.setCustomValidity("");
×
UNCOV
173
    }
×
174
    if (typeof onChange === "function") {
×
175
      onChange(e, v, r);
×
UNCOV
176
    }
×
177

178
    setTypeVal(v);
×
UNCOV
179
  };
×
180
  const onExtensionValChangeWrapper = (e, v, r) => {
×
181
    setShowFileExtensionError(false);
×
182
    v = v || "";
×
183
    if (v === "") {
×
184
      fileExtensionRef.current.setCustomValidity("Please specify a file extension type");
×
UNCOV
185
    } else {
×
186
      fileExtensionRef.current.setCustomValidity("");
×
UNCOV
187
    }
×
188
    if (typeof onChange === "function") {
×
189
      onChange(e, v, r);
×
UNCOV
190
    }
×
191

192
    setExtensionVal(v);
×
UNCOV
193
  };
×
194
  const typeTextInputOnChange = (r) => {
×
195
    onTypeValChangeWrapper(null, r.target.value, null);
×
UNCOV
196
  };
×
197
  const extensionTextInputOnChange = (r) => {
×
198
    onExtensionValChangeWrapper(null, r.target.value, null);
×
UNCOV
199
  };
×
200

201
  useEffect(() => {
×
202
    onTypeValChangeWrapper(null, typeValue, null);
×
UNCOV
203
  }, [typeValue]);
×
204

205
  useEffect(() => {
×
206
    onExtensionValChangeWrapper(null, extensionValue, null);
×
UNCOV
207
  }, [extensionValue]);
×
208

209
  return (
×
UNCOV
210
    <>
×
UNCOV
211
      <StyledTableCell>
×
UNCOV
212
        <StyledAutocomplete
×
UNCOV
213
          sx={{
×
UNCOV
214
            "& .MuiInputBase-input": {
×
UNCOV
215
              fontWeight: 400,
×
UNCOV
216
              fontSize: "16px",
×
UNCOV
217
              fontFamily: "'Nunito', 'Rubik', sans-serif",
×
UNCOV
218
              padding: "0 !important",
×
UNCOV
219
              height: "20px",
×
UNCOV
220
            },
×
UNCOV
221
          }}
×
UNCOV
222
          id={inputID.concat("-type")}
×
UNCOV
223
          size="small"
×
UNCOV
224
          value={typeVal || ""}
×
UNCOV
225
          onChange={onTypeValChangeWrapper}
×
UNCOV
226
          popupIcon={<DropdownArrowsIcon />}
×
UNCOV
227
          readOnly={readOnlyInputs}
×
UNCOV
228
          freeSolo
×
UNCOV
229
          PaperComponent={StyledPaper}
×
UNCOV
230
          slotProps={{
×
UNCOV
231
            popper: {
×
UNCOV
232
              disablePortal: true,
×
UNCOV
233
              sx: {
×
UNCOV
234
                top: "-2px !important",
×
UNCOV
235
                zIndex: "2000",
×
UNCOV
236
              },
×
UNCOV
237
              modifiers: [
×
UNCOV
238
                {
×
239
                  // disables popper from flipping above the input when out of screen room
UNCOV
240
                  name: "flip",
×
UNCOV
241
                  enabled: false,
×
UNCOV
242
                  options: {
×
UNCOV
243
                    fallbackPlacements: [],
×
UNCOV
244
                  },
×
UNCOV
245
                },
×
246
              ],
UNCOV
247
            },
×
UNCOV
248
          }}
×
UNCOV
249
          renderInput={(p) => (
×
250
            <StyledTooltip
×
UNCOV
251
              title="Missing required field"
×
UNCOV
252
              arrow
×
UNCOV
253
              disableHoverListener
×
UNCOV
254
              disableFocusListener
×
UNCOV
255
              disableTouchListener
×
UNCOV
256
              open={showFileTypeEror}
×
UNCOV
257
              slotProps={{
×
UNCOV
258
                tooltip: { style: { marginTop: "4px !important" } },
×
UNCOV
259
              }}
×
260
            >
UNCOV
261
              <TextField
×
UNCOV
262
                {...p}
×
UNCOV
263
                onChange={typeTextInputOnChange}
×
UNCOV
264
                name={name.concat("[type]")}
×
UNCOV
265
                required={required}
×
UNCOV
266
                placeholder={rest.placeholder || "Enter or select a type"}
×
UNCOV
267
                variant="standard"
×
UNCOV
268
                inputRef={fileTypeRef}
×
UNCOV
269
                InputProps={{ ...p.InputProps, disableUnderline: true }}
×
270
                // eslint-disable-next-line react/jsx-no-duplicate-props
UNCOV
271
                inputProps={{
×
UNCOV
272
                  ...p.inputProps,
×
UNCOV
273
                  maxLength: 30,
×
UNCOV
274
                  "aria-label": "File type",
×
UNCOV
275
                }}
×
276
              />
UNCOV
277
            </StyledTooltip>
×
278
          )}
UNCOV
279
          {...rest}
×
280
        />
UNCOV
281
      </StyledTableCell>
×
UNCOV
282
      <StyledTableCell>
×
UNCOV
283
        <StyledAutocomplete
×
UNCOV
284
          sx={{
×
UNCOV
285
            "& .MuiInputBase-input": {
×
UNCOV
286
              fontWeight: 400,
×
UNCOV
287
              fontSize: "16px",
×
UNCOV
288
              fontFamily: "'Nunito', 'Rubik', sans-serif",
×
UNCOV
289
              padding: "0 !important",
×
UNCOV
290
              height: "20px",
×
UNCOV
291
            },
×
UNCOV
292
          }}
×
UNCOV
293
          id={inputID.concat("-extension")}
×
UNCOV
294
          size="small"
×
UNCOV
295
          value={extensionVal || ""}
×
UNCOV
296
          onChange={onExtensionValChangeWrapper}
×
UNCOV
297
          popupIcon={<DropdownArrowsIcon />}
×
UNCOV
298
          readOnly={readOnlyInputs}
×
UNCOV
299
          freeSolo
×
UNCOV
300
          PaperComponent={StyledPaper}
×
UNCOV
301
          slotProps={{
×
UNCOV
302
            popper: {
×
UNCOV
303
              disablePortal: true,
×
UNCOV
304
              sx: {
×
UNCOV
305
                top: "-2px !important",
×
UNCOV
306
                zIndex: "2000",
×
UNCOV
307
              },
×
UNCOV
308
              modifiers: [
×
UNCOV
309
                {
×
310
                  // disables popper from flipping above the input when out of screen room
UNCOV
311
                  name: "flip",
×
UNCOV
312
                  enabled: false,
×
UNCOV
313
                  options: {
×
UNCOV
314
                    fallbackPlacements: [],
×
UNCOV
315
                  },
×
UNCOV
316
                },
×
317
              ],
UNCOV
318
            },
×
UNCOV
319
          }}
×
UNCOV
320
          renderInput={(p) => (
×
321
            <StyledTooltip
×
UNCOV
322
              title="Missing required field"
×
UNCOV
323
              arrow
×
UNCOV
324
              disableHoverListener
×
UNCOV
325
              disableFocusListener
×
UNCOV
326
              disableTouchListener
×
UNCOV
327
              open={showFileExtensionError}
×
328
            >
UNCOV
329
              <TextField
×
UNCOV
330
                {...p}
×
UNCOV
331
                onChange={extensionTextInputOnChange}
×
UNCOV
332
                name={name.concat("[extension]")}
×
UNCOV
333
                required={required}
×
UNCOV
334
                placeholder={rest.placeholder || "Enter or select an extension"}
×
UNCOV
335
                variant="standard"
×
UNCOV
336
                inputRef={fileExtensionRef}
×
UNCOV
337
                InputProps={{ ...p.InputProps, disableUnderline: true }}
×
338
                // eslint-disable-next-line react/jsx-no-duplicate-props
UNCOV
339
                inputProps={{
×
UNCOV
340
                  ...p.inputProps,
×
UNCOV
341
                  maxLength: 10,
×
UNCOV
342
                  "aria-label": "File extension",
×
UNCOV
343
                }}
×
344
              />
UNCOV
345
            </StyledTooltip>
×
346
          )}
UNCOV
347
          options={fileTypeExtensions[typeVal] || []}
×
348
        />
UNCOV
349
      </StyledTableCell>
×
UNCOV
350
    </>
×
351
  );
UNCOV
352
};
×
353

354
export default TableAutocompleteInput;
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc