• 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

88.56
/src/components/Questionnaire/TextInput.tsx
1
import {
1✔
2
  FormControl,
3
  FormHelperText,
4
  Grid,
5
  OutlinedInput,
6
  OutlinedInputProps,
7
  styled,
8
} from "@mui/material";
9
import React, { FC, ReactNode, useEffect, useId, useRef, useState } from "react";
1✔
10

11
import { updateInputValidity } from "../../utils";
1✔
12
import Tooltip from "../Tooltip";
1✔
13

14
const StyledGridWrapper = styled(Grid)(({ theme }) => ({
1✔
15
  "& .MuiFormHelperText-root": {
1,098✔
16
    color: "#083A50",
1,098✔
17
    marginLeft: "0",
1,098✔
18
    [theme.breakpoints.up("lg")]: {
1,098✔
19
      whiteSpace: "nowrap",
1,098✔
20
    },
1,098✔
21
  },
1,098✔
22
  "& .MuiFormHelperText-root.Mui-error": {
1,098✔
23
    color: "#D54309 !important",
1,098✔
24
  },
1,098✔
25
}));
1✔
26

27
const StyledFormControl = styled(FormControl)(() => ({
1✔
28
  height: "100%",
1,098✔
29
  justifyContent: "end",
1,098✔
30
}));
1✔
31

32
export const StyledLabelWrapper = styled("div")(() => ({
1✔
33
  display: "flex",
1,098✔
34
  justifyContent: "flex-start",
1,098✔
35
  alignItems: "center",
1,098✔
36
  minHeight: "20px",
1,098✔
37
  color: "#083A50",
1,098✔
38
  marginBottom: "4px",
1,098✔
39
}));
1✔
40

41
export const StyledLabel = styled("label")(() => ({
1✔
42
  fontWeight: 700,
147✔
43
  fontSize: "16px",
147✔
44
  lineHeight: "19.6px",
147✔
45
  minHeight: "20px",
147✔
46
  color: "#083A50",
147✔
47
}));
1✔
48

49
const StyledAsterisk = styled("span")(() => ({
1✔
UNCOV
50
  color: "#C93F08",
×
UNCOV
51
  marginLeft: "2px",
×
52
}));
1✔
53

54
const StyledHelperText = styled(FormHelperText)(() => ({
1✔
55
  marginTop: "4px",
1,098✔
56
  minHeight: "20px",
1,098✔
57
}));
1✔
58

59
const StyledOutlinedInput = styled(OutlinedInput, {
1✔
60
  shouldForwardProp: (prop) => prop !== "resize" && prop !== "rowHeight",
1✔
61
})<OutlinedInputProps & { resize: boolean; rowHeight: number }>(
1✔
62
  ({ resize, rowHeight, rows, minRows, maxRows }) => ({
1✔
63
    borderRadius: "8px",
1,098✔
64
    backgroundColor: "#fff",
1,098✔
65
    color: "#083A50",
1,098✔
66
    "& .MuiInputBase-input": {
1,098✔
67
      fontWeight: 400,
1,098✔
68
      fontSize: "16px",
1,098✔
69
      fontFamily: "'Nunito', 'Rubik', sans-serif",
1,098✔
70
      lineHeight: "19.6px",
1,098✔
71
      padding: "12px",
1,098✔
72
      height: "20px",
1,098✔
73
    },
1,098✔
74
    "&.MuiInputBase-multiline": {
1,098✔
75
      padding: "12px",
1,098✔
76
    },
1,098✔
77
    "& .MuiInputBase-inputMultiline": {
1,098✔
78
      resize: resize ? "vertical" : "none",
1,098!
79
      minHeight: resize && rowHeight ? `${(+rows || +minRows || 1) * rowHeight}px` : 0,
1,098!
80
      maxHeight: resize && maxRows && rowHeight ? `${+maxRows * rowHeight}px` : "none",
1,098!
81
    },
1,098✔
82
    "&.MuiInputBase-multiline .MuiInputBase-input": {
1,098✔
83
      lineHeight: `${rowHeight}px`,
1,098✔
84
      padding: 0,
1,098✔
85
    },
1,098✔
86
    "& .MuiOutlinedInput-notchedOutline": {
1,098✔
87
      borderColor: "#6B7294",
1,098✔
88
    },
1,098✔
89
    "&.Mui-focused .MuiOutlinedInput-notchedOutline": {
1,098✔
90
      border: "1px solid #209D7D",
1,098✔
91
      boxShadow:
1,098✔
92
        "2px 2px 4px 0px rgba(38, 184, 147, 0.10), -1px -1px 6px 0px rgba(38, 184, 147, 0.20)",
1,098✔
93
    },
1,098✔
94
    "& .MuiInputBase-input::placeholder": {
1,098✔
95
      color: "#87878C",
1,098✔
96
      fontWeight: 400,
1,098✔
97
      opacity: 1,
1,098✔
98
    },
1,098✔
99
    // Override the input error border color
100
    "&.Mui-error fieldset": {
1,098✔
101
      borderColor: "#D54309 !important",
1,098✔
102
    },
1,098✔
103
    // Target readOnly <textarea> inputs
104
    "&.MuiInputBase-multiline.Mui-readOnly": {
1,098✔
105
      backgroundColor: "#E5EEF4",
1,098✔
106
      color: "#083A50",
1,098✔
107
      cursor: "not-allowed",
1,098✔
108
      borderRadius: "8px",
1,098✔
109
    },
1,098✔
110
    // Target readOnly <input> inputs
111
    "& .MuiOutlinedInput-input:read-only": {
1,098✔
112
      backgroundColor: "#E5EEF4",
1,098✔
113
      color: "#083A50",
1,098✔
114
      cursor: "not-allowed",
1,098✔
115
      borderRadius: "8px",
1,098✔
116
    },
1,098✔
117
  })
1,098✔
118
);
1✔
119

120
type Props = {
121
  label?: string | ReactNode;
122
  labelStartAdornment?: ReactNode;
123
  labelEndAdornment?: ReactNode;
124
  infoText?: string;
125
  errorText?: string;
126
  tooltipText?: string | ReactNode;
127
  gridWidth?: 2 | 4 | 6 | 8 | 10 | 12;
128
  maxLength?: number;
129
  hideValidation?: boolean;
130
  resize?: boolean;
131
  validate?: (input: string) => boolean;
132
  filter?: (input: string) => string;
133
  parentStateSetter?: (string) => void;
134
} & OutlinedInputProps;
135

136
/**
137
 * Generates a generic text input with a label and help text
138
 *
139
 * NOTE:
140
 * - We're using a custom wrapper for Material UI's OutlinedInput component
141
 *   instead of using the TextField component because of the forced
142
 *   floating label behavior of TextField.
143
 *
144
 * @param {Props} props & number props
145
 * @returns {JSX.Element}
146
 */
147
const TextInput: FC<Props> = ({
1✔
148
  classes,
2,196✔
149
  value,
2,196✔
150
  label,
2,196✔
151
  labelStartAdornment,
2,196✔
152
  labelEndAdornment,
2,196✔
153
  required = false,
2,196✔
154
  gridWidth,
2,196✔
155
  maxLength,
2,196✔
156
  infoText,
2,196✔
157
  errorText,
2,196✔
158
  tooltipText,
2,196✔
159
  hideValidation,
2,196✔
160
  validate,
2,196✔
161
  filter,
2,196✔
162
  type,
2,196✔
163
  readOnly,
2,196✔
164
  rows,
2,196✔
165
  multiline,
2,196✔
166
  resize,
2,196✔
167
  inputProps,
2,196✔
168
  onChange,
2,196✔
169
  parentStateSetter,
2,196✔
170
  ...rest
2,196✔
171
}) => {
2,196✔
172
  const id = rest.id || useId();
2,196✔
173

174
  const [val, setVal] = useState(value);
2,196✔
175
  const [error, setError] = useState(false);
2,196✔
176
  const errorMsg = errorText || (required ? "This field is required" : null);
2,196!
177
  const inputRef = useRef<HTMLInputElement>(null);
2,196✔
178
  const ROW_HEIGHT = 25; // line height of each row in a multiline
2,196✔
179

180
  const processValue = (inputVal: string) => {
2,196✔
181
    let newVal = inputVal;
2,224✔
182

183
    if (typeof filter === "function") {
2,224!
184
      newVal = filter(newVal);
×
UNCOV
185
    }
×
186
    if (typeof maxLength === "number" && newVal?.length > maxLength) {
2,224✔
187
      newVal = newVal.slice(0, maxLength);
102✔
188
    }
102✔
189
    if (typeof validate === "function") {
2,224!
190
      const customIsValid = validate(newVal);
×
191
      updateInputValidity(inputRef, !customIsValid ? errorMsg : "");
×
UNCOV
192
    }
×
193
    if (typeof parentStateSetter === "function") {
2,224!
194
      parentStateSetter(newVal);
×
UNCOV
195
    }
×
196
    setVal(newVal);
2,224✔
197
  };
2,224✔
198

199
  const onChangeWrapper = (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
2,196✔
200
    if (typeof onChange === "function") {
1,150✔
201
      onChange(event);
1,150✔
202
    }
1,150✔
203
    const newVal = event.target.value;
1,150✔
204

205
    processValue(newVal);
1,150✔
206
    setError(false);
1,150✔
207
  };
1,150✔
208

209
  useEffect(() => {
2,196✔
210
    const invalid = () => setError(true);
24✔
211

212
    inputRef.current?.addEventListener("invalid", invalid);
24✔
213
    return () => {
24✔
214
      inputRef.current?.removeEventListener("invalid", invalid);
24!
215
    };
24✔
216
  }, [inputRef]);
2,196✔
217

218
  useEffect(() => {
2,196✔
219
    processValue(value?.toString());
1,074✔
220
  }, [value]);
2,196✔
221

222
  /* MUI sets the height for multiline input using inline styling. Needs to be overwritten to have a working minHeight */
223
  const customInputProps =
2,196✔
224
    resize && multiline
2,196✔
225
      ? { style: { height: `${(+rows || 1) * ROW_HEIGHT}px`, overflow: "auto" } }
2,196!
UNCOV
226
      : {};
×
227

228
  return (
2,196✔
229
    <StyledGridWrapper md={gridWidth || 6} xs={12} item>
2,196✔
230
      <StyledFormControl fullWidth error={error}>
2,196✔
231
        <StyledLabelWrapper>
2,196✔
232
          {labelStartAdornment}
2,196✔
233
          {label && (
2,196!
UNCOV
234
            <StyledLabel htmlFor={id}>
×
UNCOV
235
              {label}
×
UNCOV
236
              {required && label ? <StyledAsterisk>*</StyledAsterisk> : ""}
×
UNCOV
237
              {tooltipText && <Tooltip placement="right" title={tooltipText} />}
×
UNCOV
238
            </StyledLabel>
×
239
          )}
240
          {labelEndAdornment}
2,196✔
241
        </StyledLabelWrapper>
2,196✔
242
        <StyledOutlinedInput
2,196✔
243
          inputRef={inputRef}
2,196✔
244
          type={type || "text"}
2,196✔
245
          size="small"
2,196✔
246
          value={val ?? ""}
2,196!
247
          onChange={onChangeWrapper}
2,196✔
248
          required={required}
2,196✔
249
          readOnly={readOnly}
2,196✔
250
          rows={rows}
2,196✔
251
          multiline={multiline}
2,196✔
252
          resize={resize}
2,196✔
253
          rowHeight={ROW_HEIGHT}
2,196✔
254
          {...rest}
2,196✔
255
          inputProps={{ ...customInputProps, ...inputProps }}
2,196✔
256
          id={id}
2,196✔
257
        />
258
        <StyledHelperText>
2,196✔
259
          {(!hideValidation && !readOnly && error ? errorMsg : infoText) || " "}
2,196✔
260
        </StyledHelperText>
2,196✔
261
      </StyledFormControl>
2,196✔
262
    </StyledGridWrapper>
2,196✔
263
  );
264
};
2,196✔
265

266
export default TextInput;
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