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

CBIIT / crdc-datahub-ui / 16177180347

09 Jul 2025 06:23PM UTC coverage: 62.703% (+2.4%) from 60.276%
16177180347

push

github

web-flow
Merge pull request #762 from CBIIT/3.3.0

3.3.0 Release

3560 of 6102 branches covered (58.34%)

Branch coverage included in aggregate %.

676 of 941 new or added lines in 92 files covered. (71.84%)

17 existing lines in 10 files now uncovered.

4920 of 7422 relevant lines covered (66.29%)

227.63 hits per line

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

4.85
/src/components/Questionnaire/SwitchInput.tsx
1
import React, {
2
  FC,
3
  HTMLProps,
4
  ReactElement,
5
  useEffect,
6
  useId,
7
  useMemo,
8
  useRef,
9
  useState,
10
} from "react";
11
import { SwitchProps, Grid, Switch, FormHelperText, styled, SxProps } from "@mui/material";
12
import Tooltip from "../Tooltip";
13
import { updateInputValidity } from "../../utils";
14

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

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

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

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

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

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

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

221
  // Validation if touch is required
222
  useEffect(() => {
×
223
    if (!touchRequired || readOnly) {
×
224
      return;
×
225
    }
226
    if (!touched) {
×
227
      updateInputValidity(inputRef, errorMsg);
×
228
      return;
×
229
    }
230

231
    updateInputValidity(inputRef);
×
232
  }, [touched, touchRequired]);
233

234
  const onChangeWrapper = (event: React.ChangeEvent<HTMLInputElement>, checked: boolean) => {
×
235
    if (readOnly) {
×
236
      return;
×
237
    }
238
    if (typeof onChange === "function") {
×
239
      onChange(event, checked);
×
240
    }
241
    if (!touched) {
×
242
      setTouched(true);
×
243
    }
244

245
    setVal(checked);
×
246
    setError(false);
×
247
  };
248

249
  useEffect(() => {
×
250
    const invalid = () => setError(true);
×
251

252
    inputRef.current?.addEventListener("invalid", invalid);
×
253
    return () => {
×
254
      inputRef.current?.removeEventListener("invalid", invalid);
×
255
    };
256
  }, [inputRef]);
257

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

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