• 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

9.42
/src/components/Questionnaire/FormGroupCheckbox.tsx
1
import { FormControl, FormGroup, FormHelperText, Grid, styled } from "@mui/material";
1✔
2
import { FC, useEffect, useId, useRef, useState } from "react";
1✔
3

4
import { updateInputValidity } from "../../utils";
1✔
5
import Tooltip from "../Tooltip";
1✔
6

7
import CheckboxInput from "./CheckboxInput";
1✔
8

9
const StyledFormLabel = styled("label")(({ theme }) => ({
1✔
UNCOV
10
  fontWeight: 700,
×
UNCOV
11
  fontSize: "16px",
×
UNCOV
12
  lineHeight: "19.6px",
×
UNCOV
13
  minHeight: "20px",
×
UNCOV
14
  color: "#083A50",
×
UNCOV
15
  marginBottom: "4px",
×
UNCOV
16
  [theme.breakpoints.up("lg")]: {
×
UNCOV
17
    whiteSpace: "nowrap",
×
UNCOV
18
  },
×
19
}));
1✔
20

21
const StyledAsterisk = styled("span")(() => ({
1✔
UNCOV
22
  color: "#C93F08",
×
UNCOV
23
  marginLeft: "2px",
×
24
}));
1✔
25

26
const StyledFormHelperText = styled(FormHelperText)(() => ({
1✔
UNCOV
27
  marginLeft: 0,
×
28
}));
1✔
29

30
type Props = {
31
  idPrefix?: string;
32
  label?: string | JSX.Element;
33
  hideLabel?: boolean;
34
  value: string[];
35
  name?: string;
36
  options: FormGroupCheckboxOption[];
37
  required?: boolean; // at least one checkbox needs to be checked
38
  allowMultipleChecked?: boolean;
39
  orientation?: "vertical" | "horizontal";
40
  helpText?: string;
41
  tooltipText?: string;
42
  gridWidth?: 2 | 4 | 6 | 8 | 10 | 12;
43
  readOnly?: boolean;
44
  onChange?: (values: string[]) => void;
45
};
46

47
const FormGroupCheckbox: FC<Props> = ({
1✔
UNCOV
48
  idPrefix = "",
×
UNCOV
49
  label,
×
UNCOV
50
  hideLabel,
×
UNCOV
51
  value,
×
UNCOV
52
  name,
×
UNCOV
53
  options,
×
UNCOV
54
  required = false,
×
UNCOV
55
  allowMultipleChecked = true,
×
UNCOV
56
  orientation = "vertical",
×
UNCOV
57
  helpText,
×
UNCOV
58
  tooltipText,
×
UNCOV
59
  gridWidth,
×
UNCOV
60
  readOnly,
×
UNCOV
61
  onChange,
×
UNCOV
62
}) => {
×
63
  const id = useId();
×
64

65
  const [val, setVal] = useState(value ?? []);
×
66
  const [error, setError] = useState(false);
×
UNCOV
67
  const helperText =
×
68
    helpText ||
×
UNCOV
69
    (required && !val?.length && "This field is required") ||
×
UNCOV
70
    (!allowMultipleChecked && val?.length > 1 ? "Please select only one option" : " ");
×
71
  const firstCheckboxInputRef = useRef<HTMLInputElement>(null);
×
72

73
  const onChangeWrapper = (newVal: string[]) => {
×
74
    if (typeof onChange === "function") {
×
75
      onChange(newVal);
×
UNCOV
76
    }
×
77

78
    setVal(newVal);
×
UNCOV
79
  };
×
80

81
  const handleChange = (selectedValue: string, checked: boolean) => {
×
82
    const currentVal = val || [];
×
83
    const updatedValues = checked
×
UNCOV
84
      ? [...currentVal, selectedValue]
×
85
      : currentVal?.filter((v) => v !== selectedValue);
×
86

87
    onChangeWrapper(updatedValues);
×
UNCOV
88
  };
×
89

90
  useEffect(() => {
×
91
    if (value) {
×
92
      onChangeWrapper(value);
×
UNCOV
93
    }
×
UNCOV
94
  }, [value]);
×
95

96
  useEffect(() => {
×
97
    const notSelectedAndRequired = required && !val?.length;
×
98
    const multipleChecked = val?.length > 1;
×
99

100
    if (notSelectedAndRequired) {
×
101
      updateInputValidity(firstCheckboxInputRef, "Please select at least one option");
×
102
      return;
×
UNCOV
103
    }
×
104

105
    if (!allowMultipleChecked && multipleChecked) {
×
106
      updateInputValidity(firstCheckboxInputRef, "Please select only one option");
×
107
      return;
×
UNCOV
108
    }
×
109

110
    updateInputValidity(firstCheckboxInputRef);
×
111
    setError(false);
×
UNCOV
112
  }, [val]);
×
113

114
  useEffect(() => {
×
115
    const invalid = () => setError(true);
×
116

117
    firstCheckboxInputRef.current?.addEventListener("invalid", invalid);
×
118
    return () => {
×
119
      firstCheckboxInputRef.current?.removeEventListener("invalid", invalid);
×
UNCOV
120
    };
×
UNCOV
121
  }, [firstCheckboxInputRef]);
×
122

123
  return (
×
UNCOV
124
    <Grid md={gridWidth || 6} xs={12} item>
×
UNCOV
125
      <FormControl fullWidth error={error}>
×
UNCOV
126
        {!hideLabel && (
×
UNCOV
127
          <StyledFormLabel htmlFor={id} id={`${id}-label`}>
×
UNCOV
128
            {label}
×
UNCOV
129
            {required ? <StyledAsterisk>*</StyledAsterisk> : ""}
×
UNCOV
130
            {tooltipText && <Tooltip title={tooltipText} />}
×
UNCOV
131
          </StyledFormLabel>
×
132
        )}
UNCOV
133
        <FormGroup row={orientation === "horizontal"}>
×
UNCOV
134
          {options.map((option, index) => {
×
135
            const isChecked = val?.includes(option.value);
×
136
            return (
×
UNCOV
137
              <CheckboxInput
×
UNCOV
138
                id={idPrefix.concat(`-${option.label.toLowerCase().replace(" ", "-")}-checkbox`)}
×
UNCOV
139
                aria-labelledby={`${id}-label`}
×
UNCOV
140
                key={option.value}
×
UNCOV
141
                name={name}
×
UNCOV
142
                checked={isChecked}
×
UNCOV
143
                value={option.value}
×
UNCOV
144
                inputLabel={option.label}
×
UNCOV
145
                inputLabelTooltipText={option.tooltipText}
×
UNCOV
146
                errorText={option.errorText}
×
UNCOV
147
                onChange={handleChange}
×
UNCOV
148
                readOnly={readOnly}
×
UNCOV
149
                inputRef={(ref) => {
×
150
                  if (index === 0) {
×
151
                    firstCheckboxInputRef.current = ref;
×
UNCOV
152
                  }
×
UNCOV
153
                }}
×
154
              />
155
            );
UNCOV
156
          })}
×
UNCOV
157
        </FormGroup>
×
158

159
        {/* NOTE: This is a proxy element for form parsing purposes.
160
          Also, if parent has shared name then it will use string[] as value,
161
          otherwise value will be of type boolean for the form parser */}
UNCOV
162
        {!name &&
×
UNCOV
163
          options.map((option) => {
×
164
            const isChecked = val?.includes(option.value);
×
165
            return (
×
UNCOV
166
              <input
×
UNCOV
167
                key={option.value}
×
UNCOV
168
                name={option.name}
×
UNCOV
169
                type="checkbox"
×
UNCOV
170
                data-type="boolean"
×
UNCOV
171
                value={isChecked ? "true" : "false"}
×
UNCOV
172
                onChange={() => {}}
×
UNCOV
173
                aria-labelledby={`${id}-label`}
×
UNCOV
174
                checked
×
UNCOV
175
                hidden
×
176
              />
177
            );
UNCOV
178
          })}
×
UNCOV
179
        <StyledFormHelperText>{error ? helperText : " "}</StyledFormHelperText>
×
UNCOV
180
      </FormControl>
×
UNCOV
181
    </Grid>
×
182
  );
UNCOV
183
};
×
184

185
export default FormGroupCheckbox;
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