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

CBIIT / crdc-datahub-ui / 26241948143

21 May 2026 05:23PM UTC coverage: 84.756% (+5.7%) from 79.008%
26241948143

push

github

web-flow
Merge pull request #998 from CBIIT/3.6.0

3.6.0 Release

6022 of 6644 branches covered (90.64%)

Branch coverage included in aggregate %.

3572 of 3715 new or added lines in 81 files covered. (96.15%)

15 existing lines in 6 files now uncovered.

35928 of 42851 relevant lines covered (83.84%)

243.57 hits per line

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

94.96
/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,100✔
16
    color: "#083A50",
1,100✔
17
    marginLeft: "0",
1,100✔
18
    [theme.breakpoints.up("lg")]: {
1,100✔
19
      whiteSpace: "nowrap",
1,100✔
20
    },
1,100✔
21
  },
1,100✔
22
  "& .MuiFormHelperText-root.Mui-error": {
1,100✔
23
    color: "#D54309 !important",
1,100✔
24
  },
1,100✔
25
}));
1✔
26

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

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

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

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

54
const StyledHelperText = styled(FormHelperText)(() => ({
1✔
55
  marginTop: "4px",
1,100✔
56
  minHeight: "20px",
1,100✔
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,100✔
64
    backgroundColor: "#fff",
1,100✔
65
    color: "#083A50",
1,100✔
66
    "& .MuiInputBase-input": {
1,100✔
67
      fontWeight: 400,
1,100✔
68
      fontSize: "16px",
1,100✔
69
      fontFamily: "'Nunito', 'Rubik', sans-serif",
1,100✔
70
      lineHeight: "19.6px",
1,100✔
71
      padding: "12px",
1,100✔
72
      height: "20px",
1,100✔
73
    },
1,100✔
74
    "&.MuiInputBase-multiline": {
1,100✔
75
      padding: "12px",
1,100✔
76
    },
1,100✔
77
    "& .MuiInputBase-inputMultiline": {
1,100✔
78
      resize: resize ? "vertical" : "none",
1,100✔
79
      minHeight: resize && rowHeight ? `${(+rows || +minRows || 1) * rowHeight}px` : 0,
1,100!
80
      maxHeight: resize && maxRows && rowHeight ? `${+maxRows * rowHeight}px` : "none",
1,100!
81
    },
1,100✔
82
    "&.MuiInputBase-multiline .MuiInputBase-input": {
1,100✔
83
      lineHeight: `${rowHeight}px`,
1,100✔
84
      padding: 0,
1,100✔
85
    },
1,100✔
86
    "& .MuiOutlinedInput-notchedOutline": {
1,100✔
87
      borderColor: "#6B7294",
1,100✔
88
    },
1,100✔
89
    "&.Mui-focused .MuiOutlinedInput-notchedOutline": {
1,100✔
90
      border: "1px solid #209D7D",
1,100✔
91
      boxShadow:
1,100✔
92
        "2px 2px 4px 0px rgba(38, 184, 147, 0.10), -1px -1px 6px 0px rgba(38, 184, 147, 0.20)",
1,100✔
93
    },
1,100✔
94
    "& .MuiInputBase-input::placeholder": {
1,100✔
95
      color: "#87878C",
1,100✔
96
      fontWeight: 400,
1,100✔
97
      opacity: 1,
1,100✔
98
    },
1,100✔
99
    // Override the input error border color
100
    "&.Mui-error fieldset": {
1,100✔
101
      borderColor: "#D54309 !important",
1,100✔
102
    },
1,100✔
103
    // Target readOnly <textarea> inputs
104
    "&.MuiInputBase-multiline.Mui-readOnly": {
1,100✔
105
      backgroundColor: "#E5EEF4",
1,100✔
106
      color: "#083A50",
1,100✔
107
      cursor: "not-allowed",
1,100✔
108
      borderRadius: "8px",
1,100✔
109
    },
1,100✔
110
    // Target readOnly <input> inputs
111
    "& .MuiOutlinedInput-input:read-only": {
1,100✔
112
      backgroundColor: "#E5EEF4",
1,100✔
113
      color: "#083A50",
1,100✔
114
      cursor: "not-allowed",
1,100✔
115
      borderRadius: "8px",
1,100✔
116
    },
1,100✔
117
  })
1,100✔
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,
1,365✔
149
  value,
1,365✔
150
  label,
1,365✔
151
  labelStartAdornment,
1,365✔
152
  labelEndAdornment,
1,365✔
153
  required = false,
1,365✔
154
  gridWidth,
1,365✔
155
  maxLength,
1,365✔
156
  infoText,
1,365✔
157
  errorText,
1,365✔
158
  tooltipText,
1,365✔
159
  hideValidation,
1,365✔
160
  validate,
1,365✔
161
  filter,
1,365✔
162
  type,
1,365✔
163
  readOnly,
1,365✔
164
  rows,
1,365✔
165
  multiline,
1,365✔
166
  resize,
1,365✔
167
  inputProps,
1,365✔
168
  onChange,
1,365✔
169
  parentStateSetter,
1,365✔
170
  ...rest
1,365✔
171
}) => {
1,365✔
172
  const id = rest.id || useId();
1,365!
173

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

180
  const processValue = (inputVal: string) => {
1,365✔
181
    let newVal = inputVal;
617✔
182

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

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

205
    processValue(newVal);
8✔
206
    setError(false);
8✔
207
  };
8✔
208

209
  useEffect(() => {
1,365✔
210
    const invalid = () => setError(true);
607✔
211

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

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

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

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