• 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.64
/src/components/Questionnaire/SwitchInput.tsx
1
import { SwitchProps, Grid, Switch, FormHelperText, styled, SxProps } from "@mui/material";
1✔
2
import React, {
1✔
3
  FC,
4
  HTMLProps,
5
  ReactElement,
6
  useEffect,
7
  useId,
8
  useMemo,
9
  useRef,
10
  useState,
11
} from "react";
12

13
import { updateInputValidity } from "../../utils";
1✔
14
import Tooltip from "../Tooltip";
1✔
15

16
const GridStyled = styled(Grid, { shouldForwardProp: (p) => p !== "switchSx" })<{
1✔
17
  switchSx: SxProps;
18
}>(({ theme, switchSx }) => ({
1✔
UNCOV
19
  "& .switchRoot": {
×
UNCOV
20
    width: "65px",
×
UNCOV
21
    height: "35px",
×
UNCOV
22
    padding: 0,
×
UNCOV
23
  },
×
UNCOV
24
  "& .switchBase": {
×
UNCOV
25
    paddingTop: "5px",
×
UNCOV
26
    paddingLeft: "7px",
×
UNCOV
27
    transitionDuration: "300ms",
×
UNCOV
28
    "&.Mui-checked": {
×
UNCOV
29
      transform: "translateX(26px)",
×
UNCOV
30
      "& + .MuiSwitch-track": {
×
UNCOV
31
        border: "1.25px solid #6A6A6A",
×
UNCOV
32
      },
×
UNCOV
33
    },
×
UNCOV
34
    "&.focusVisible, &:focus, &:active": {
×
UNCOV
35
      "& + .track": {
×
UNCOV
36
        border: "1.25px solid #6A6A6A !important",
×
UNCOV
37
      },
×
UNCOV
38
    },
×
UNCOV
39
  },
×
UNCOV
40
  "& .thumb": {
×
UNCOV
41
    color: "#08A0B4",
×
UNCOV
42
    width: "25px",
×
UNCOV
43
    height: "25px",
×
UNCOV
44
    boxShadow: "none",
×
UNCOV
45
    transition: "color 300ms ease",
×
UNCOV
46
  },
×
UNCOV
47
  "& .Mui-checked .thumb": {
×
UNCOV
48
    color: "#FFFFFF",
×
UNCOV
49
  },
×
UNCOV
50
  "& .MuiSwitch-track.track": {
×
UNCOV
51
    position: "relative",
×
UNCOV
52
    borderRadius: "60px",
×
UNCOV
53
    backgroundColor: "#FFFFFF",
×
UNCOV
54
    border: "1px solid #DBDBDB",
×
UNCOV
55
    opacity: 1,
×
UNCOV
56
    overflow: "hidden",
×
UNCOV
57
    transition: theme.transitions.create(["border"], {
×
UNCOV
58
      duration: 300,
×
UNCOV
59
    }),
×
UNCOV
60
    "&::before": {
×
UNCOV
61
      position: "absolute",
×
UNCOV
62
      content: "''",
×
UNCOV
63
      inset: 0,
×
UNCOV
64
      backgroundImage: "linear-gradient(270deg, #08A0B4 49.23%, #05595E 115.38%)",
×
UNCOV
65
      zIndex: 1,
×
UNCOV
66
      opacity: 0,
×
UNCOV
67
      transition: "opacity 0.3s linear",
×
UNCOV
68
    },
×
UNCOV
69
  },
×
UNCOV
70
  "& .Mui-checked+.MuiSwitch-track.track": {
×
UNCOV
71
    borderRadius: "60px",
×
UNCOV
72
    border: "1.25px solid #6A6A6A",
×
UNCOV
73
    opacity: 1,
×
UNCOV
74
  },
×
UNCOV
75
  "& .Mui-checked+.MuiSwitch-track.track::before": {
×
UNCOV
76
    opacity: 1,
×
UNCOV
77
  },
×
UNCOV
78
  "& .readOnly .Mui-checked+.MuiSwitch-track.track::before": {
×
UNCOV
79
    opacity: 0.5,
×
UNCOV
80
  },
×
UNCOV
81
  "& .readOnly .MuiSwitch-track": {
×
UNCOV
82
    backgroundColor: "#E5EEF4 !important",
×
UNCOV
83
  },
×
UNCOV
84
  "& .readOnly .MuiSwitch-input": {
×
UNCOV
85
    cursor: "not-allowed",
×
UNCOV
86
  },
×
UNCOV
87
  "& .text": {
×
UNCOV
88
    display: "inline",
×
UNCOV
89
    fontFamily: "Lato",
×
UNCOV
90
    fontStyle: "normal",
×
UNCOV
91
    fontWeight: 600,
×
UNCOV
92
    fontSize: "16px",
×
UNCOV
93
    lineHeight: "22px",
×
UNCOV
94
    color: "#5D7B7E",
×
UNCOV
95
    marginLeft: "6px",
×
UNCOV
96
    marginRight: "6px",
×
UNCOV
97
  },
×
UNCOV
98
  "& .textChecked": {
×
UNCOV
99
    display: "inline",
×
UNCOV
100
    fontFamily: "Lato",
×
UNCOV
101
    fontStyle: "normal",
×
UNCOV
102
    fontWeight: 600,
×
UNCOV
103
    fontSize: "16px",
×
UNCOV
104
    lineHeight: "22px",
×
UNCOV
105
    color: "#4E5F63",
×
UNCOV
106
    marginLeft: "6px",
×
UNCOV
107
    marginRight: "6px",
×
UNCOV
108
  },
×
UNCOV
109
  "& .input": {
×
UNCOV
110
    display: "none",
×
UNCOV
111
  },
×
UNCOV
112
  "& .asterisk": {
×
UNCOV
113
    color: "#C93F08",
×
UNCOV
114
    marginLeft: "2px",
×
UNCOV
115
  },
×
UNCOV
116
  "& .labelContainer": {
×
UNCOV
117
    color: "#083A50",
×
UNCOV
118
    display: "flex",
×
UNCOV
119
    alignItems: "center",
×
UNCOV
120
    height: "20px",
×
UNCOV
121
  },
×
UNCOV
122
  "& .switchYesNoContainer": {
×
UNCOV
123
    display: "flex",
×
UNCOV
124
    alignItems: "center",
×
UNCOV
125
    marginRight: "28px",
×
UNCOV
126
    marginLeft: "auto",
×
UNCOV
127
    minHeight: "50px",
×
UNCOV
128
    ...switchSx,
×
UNCOV
129
  },
×
UNCOV
130
  "& .tooltip": {
×
UNCOV
131
    alignSelf: "start",
×
UNCOV
132
    marginLeft: "6px",
×
UNCOV
133
  },
×
UNCOV
134
  "& .errorMessage": {
×
UNCOV
135
    color: "#D54309 !important",
×
UNCOV
136
    marginTop: "44px",
×
UNCOV
137
    marginLeft: "8px",
×
UNCOV
138
    minHeight: "20px",
×
UNCOV
139
    width: "fit-content",
×
UNCOV
140
    position: "absolute",
×
UNCOV
141
  },
×
UNCOV
142
  "& .switchErrorContainer": {
×
UNCOV
143
    display: "flex",
×
UNCOV
144
    flexDirection: "column",
×
UNCOV
145
  },
×
146
}));
1✔
147

148
const Container = styled("div", {
1✔
149
  shouldForwardProp: (prop) => prop !== "containerWidth",
1✔
150
})<HTMLProps<HTMLDivElement> & { containerWidth?: string }>(({ containerWidth }) => ({
1✔
UNCOV
151
  display: "flex",
×
UNCOV
152
  flexDirection: "row",
×
UNCOV
153
  justifyContent: "space-between",
×
UNCOV
154
  alignItems: "center",
×
UNCOV
155
  fontSize: "16px",
×
UNCOV
156
  fontFamily: "'Nunito', 'Rubik', sans-serif",
×
UNCOV
157
  fontWeight: 700,
×
UNCOV
158
  lineHeight: "19.6px",
×
UNCOV
159
  minHeight: "50px",
×
UNCOV
160
  flexWrap: "wrap",
×
UNCOV
161
  width: containerWidth,
×
162
}));
1✔
163

164
const HideContentWrapper = styled("div")({
1✔
165
  display: "none !important",
1✔
166
});
1✔
167

168
type Props = {
169
  label: string;
170
  name: string;
171
  tooltipText?: string;
172
  required?: boolean;
173
  gridWidth?: 2 | 4 | 6 | 8 | 10 | 12;
174
  errorText?: string;
175
  value: boolean;
176
  toggleContent?: ReactElement;
177
  isBoolean?: boolean;
178
  touchRequired?: boolean;
179
  graphQLValue?: string;
180
  containerWidth?: string;
181
  /**
182
   * Provides styling override for the switch container
183
   */
184
  switchSx?: SxProps;
185
} & Omit<SwitchProps, "color">;
186

187
const CustomSwitch: FC<Props> = ({
1✔
UNCOV
188
  classes,
×
UNCOV
189
  label,
×
UNCOV
190
  required,
×
UNCOV
191
  value,
×
UNCOV
192
  onChange,
×
UNCOV
193
  name,
×
UNCOV
194
  tooltipText,
×
UNCOV
195
  gridWidth,
×
UNCOV
196
  errorText,
×
UNCOV
197
  toggleContent,
×
UNCOV
198
  graphQLValue = "",
×
UNCOV
199
  isBoolean = false,
×
UNCOV
200
  switchSx = {},
×
UNCOV
201
  containerWidth = "auto",
×
UNCOV
202
  touchRequired,
×
UNCOV
203
  readOnly,
×
UNCOV
204
  sx,
×
UNCOV
205
  ...rest
×
UNCOV
206
}) => {
×
207
  const id = rest.id || useId();
×
208

209
  const [val, setVal] = useState<boolean | null>(value);
×
210
  const [touched, setTouched] = useState(value?.toString()?.length > 0);
×
211
  const [error, setError] = useState(false);
×
212
  const errorMsg = errorText || (required ? "This field is required" : null);
×
213
  const inputRef = useRef<HTMLInputElement>(null);
×
214

215
  const proxyValue = useMemo(() => {
×
216
    if (isBoolean) {
×
217
      return touchRequired && !touched ? undefined : val?.toString();
×
UNCOV
218
    }
×
219
    return val ? graphQLValue : "";
×
UNCOV
220
  }, [isBoolean, val, graphQLValue, touched, touchRequired]);
×
221

UNCOV
222
  const onChangeWrapper = (event: React.ChangeEvent<HTMLInputElement>, checked: boolean) => {
×
223
    if (readOnly) {
×
224
      return;
×
UNCOV
225
    }
×
226
    if (typeof onChange === "function") {
×
227
      onChange(event, checked);
×
UNCOV
228
    }
×
229
    if (!touched) {
×
230
      setTouched(true);
×
UNCOV
231
    }
×
232

233
    setVal(checked);
×
234
    setError(false);
×
UNCOV
235
  };
×
236

237
  useEffect(() => {
×
238
    const invalid = () => setError(true);
×
239

240
    inputRef.current?.addEventListener("invalid", invalid);
×
241
    return () => {
×
242
      inputRef.current?.removeEventListener("invalid", invalid);
×
UNCOV
243
    };
×
UNCOV
244
  }, [inputRef]);
×
245

NEW
246
  useEffect(() => {
×
NEW
247
    if (!touchRequired || readOnly) {
×
NEW
248
      return;
×
NEW
249
    }
×
NEW
250
    if (!touched) {
×
NEW
251
      updateInputValidity(inputRef, errorMsg);
×
NEW
252
      return;
×
NEW
253
    }
×
254

NEW
255
    updateInputValidity(inputRef);
×
NEW
256
  }, [touched, touchRequired]);
×
257

NEW
258
  useEffect(() => {
×
NEW
259
    setVal(value);
×
NEW
260
  }, [value]);
×
261

UNCOV
262
  return (
×
UNCOV
263
    <GridStyled md={gridWidth || 6} xs={12} item sx={sx} switchSx={switchSx}>
×
UNCOV
264
      <Container containerWidth={containerWidth}>
×
UNCOV
265
        <div className="labelContainer">
×
UNCOV
266
          {label && (
×
UNCOV
267
            <label htmlFor={id} id={`${id}-label`}>
×
UNCOV
268
              {label}
×
UNCOV
269
            </label>
×
270
          )}
UNCOV
271
          {required ? <span className="asterisk">*</span> : ""}
×
UNCOV
272
          {tooltipText && <Tooltip placement="right" className="tooltip" title={tooltipText} />}
×
UNCOV
273
        </div>
×
UNCOV
274
        <div className="switchErrorContainer">
×
UNCOV
275
          <div className="switchYesNoContainer">
×
UNCOV
276
            <div className={val ? "text" : "textChecked"}>No</div>
×
UNCOV
277
            <Switch
×
UNCOV
278
              inputRef={inputRef}
×
UNCOV
279
              inputProps={{ datatype: "boolean" }}
×
UNCOV
280
              focusVisibleClassName="focusVisible"
×
UNCOV
281
              checked={val || false}
×
UNCOV
282
              onChange={onChangeWrapper}
×
UNCOV
283
              readOnly={readOnly}
×
UNCOV
284
              disableRipple
×
UNCOV
285
              className={readOnly ? "readOnly" : ""}
×
UNCOV
286
              classes={{
×
UNCOV
287
                root: "switchRoot",
×
UNCOV
288
                switchBase: "switchBase",
×
UNCOV
289
                thumb: "thumb",
×
UNCOV
290
                track: "track",
×
UNCOV
291
                checked: "checked",
×
UNCOV
292
              }}
×
UNCOV
293
              {...rest}
×
UNCOV
294
              id={id}
×
295
            />
296
            {/* To satisfy the form parser. The mui switch value is not good for the form parser */}
UNCOV
297
            <input
×
UNCOV
298
              onChange={() => {}}
×
UNCOV
299
              className="input"
×
UNCOV
300
              name={name}
×
UNCOV
301
              type="checkbox"
×
UNCOV
302
              data-type={isBoolean ? "boolean" : "auto"}
×
UNCOV
303
              value={proxyValue}
×
UNCOV
304
              aria-labelledby={`${id}-label`}
×
UNCOV
305
              checked
×
306
            />
UNCOV
307
            <div className={val ? "textChecked" : "text"}>Yes</div>
×
UNCOV
308
          </div>
×
UNCOV
309
          <FormHelperText className="errorMessage">
×
UNCOV
310
            {!readOnly && error ? errorMsg : " "}
×
UNCOV
311
          </FormHelperText>
×
UNCOV
312
        </div>
×
UNCOV
313
      </Container>
×
UNCOV
314
      {val ? toggleContent : <HideContentWrapper>{toggleContent}</HideContentWrapper>}
×
UNCOV
315
    </GridStyled>
×
316
  );
UNCOV
317
};
×
318

319
export default CustomSwitch;
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