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

CBIIT / crdc-datahub-ui / 11074479132

27 Sep 2024 04:44PM UTC coverage: 44.982% (+26.5%) from 18.435%
11074479132

Pull #479

github

web-flow
Merge a0867d25a into 3d8b55818
Pull Request #479: 3.0.0 Release

1727 of 4418 branches covered (39.09%)

Branch coverage included in aggregate %.

2612 of 5228 relevant lines covered (49.96%)

128.96 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

177
  const processValue = (inputVal: string) => {
×
178
    let newVal = inputVal;
×
179

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

196
  const onChangeWrapper = (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
×
197
    if (typeof onChange === "function") {
×
198
      onChange(event);
×
199
    }
200
    const newVal = event.target.value;
×
201

202
    processValue(newVal);
×
203
    setError(false);
×
204
  };
205

206
  useEffect(() => {
×
207
    const invalid = () => setError(true);
×
208

209
    inputRef.current?.addEventListener("invalid", invalid);
×
210
    return () => {
×
211
      inputRef.current?.removeEventListener("invalid", invalid);
×
212
    };
213
  }, [inputRef]);
214

215
  useEffect(() => {
×
216
    processValue(value?.toString());
×
217
  }, [value]);
218

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

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

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