Skip to content

Commit 27f2292

Browse files
FFahrenheitheloiseluiriddhybansalmaradwan26
authored
feat: Added Checkbox behavior to not show invalid / warn when readOnly / disabled (#20804)
* fix: disable invalid / warn on readOnly / disabled Checkbox #20728 * fix: disable invalid / warn on readOnly / disabled Checkbox (WC) #20728 * feat: invalid / warn on disabled tests #20728 * fix: storybook wc changes * fix(checkbox): normalize checkbox in react * test(checkbox): add tests * fix(checkbox): normalize checkbox in wc * test(checkbox): add tests in wc * Apply suggestions from code review Co-authored-by: Mahmoud <132728978+maradwan26@users.noreply.github.com> --------- Co-authored-by: Heloise Lui <71858203+heloiselui@users.noreply.github.com> Co-authored-by: Riddhi Bansal <41935566+riddhybansal@users.noreply.github.com> Co-authored-by: “heloiselui” <helolui27@gmail.com> Co-authored-by: Mahmoud <132728978+maradwan26@users.noreply.github.com>
1 parent cd595ed commit 27f2292

9 files changed

Lines changed: 592 additions & 44 deletions

File tree

packages/react/src/components/Checkbox/Checkbox.tsx

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright IBM Corp. 2016, 2025
2+
* Copyright IBM Corp. 2016, 2026
33
*
44
* This source code is licensed under the Apache-2.0 license found in the
55
* LICENSE file in the root directory of this source tree.
@@ -14,6 +14,7 @@ import { usePrefix } from '../../internal/usePrefix';
1414
import { WarningFilled, WarningAltFilled } from '@carbon/icons-react';
1515
import { useId } from '../../internal/useId';
1616
import { noopFn } from '../../internal/noopFn';
17+
import { useNormalizedInputProps } from '../../internal/useNormalizedInputProps';
1718
import { AILabel } from '../AILabel';
1819
import { isComponentElement } from '../../internal';
1920

@@ -125,15 +126,26 @@ const Checkbox = React.forwardRef(
125126
title = '',
126127
warn,
127128
warnText,
129+
disabled,
128130
slug,
129131
...other
130132
}: CheckboxProps,
131133
ref
132134
) => {
133135
const prefix = usePrefix();
134136

135-
const showWarning = !readOnly && !invalid && warn;
136-
const showHelper = !invalid && !warn;
137+
const normalizedProps = useNormalizedInputProps({
138+
id,
139+
readOnly,
140+
disabled: disabled ?? false,
141+
invalid: invalid ?? false,
142+
invalidText,
143+
warn: warn ?? false,
144+
warnText,
145+
});
146+
147+
const showWarning = normalizedProps.warn;
148+
const showHelper = !normalizedProps.invalid && !normalizedProps.warn;
137149

138150
const checkboxGroupInstanceId = useId();
139151

@@ -154,7 +166,7 @@ const Checkbox = React.forwardRef(
154166
className,
155167
{
156168
[`${prefix}--checkbox-wrapper--readonly`]: readOnly,
157-
[`${prefix}--checkbox-wrapper--invalid`]: !readOnly && invalid,
169+
[`${prefix}--checkbox-wrapper--invalid`]: normalizedProps.invalid,
158170
[`${prefix}--checkbox-wrapper--warning`]: showWarning,
159171
[`${prefix}--checkbox-wrapper--slug`]: slug,
160172
[`${prefix}--checkbox-wrapper--decorator`]: decorator,
@@ -176,8 +188,9 @@ const Checkbox = React.forwardRef(
176188
<div className={wrapperClasses}>
177189
<input
178190
{...other}
191+
disabled={disabled}
179192
type="checkbox"
180-
data-invalid={invalid ? true : undefined}
193+
data-invalid={normalizedProps.invalid ? true : undefined}
181194
onChange={(evt) => {
182195
if (!readOnly && onChange) {
183196
onChange(evt, { checked: evt.target.checked, id });
@@ -227,7 +240,7 @@ const Checkbox = React.forwardRef(
227240
</Text>
228241
</label>
229242
<div className={`${prefix}--checkbox__validation-msg`}>
230-
{!readOnly && invalid && (
243+
{normalizedProps.invalid && (
231244
<>
232245
<WarningFilled className={`${prefix}--checkbox__invalid-icon`} />
233246
<div className={`${prefix}--form-requirement`}>{invalidText}</div>

packages/react/src/components/Checkbox/__tests__/Checkbox-test.js

Lines changed: 120 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright IBM Corp. 2016, 2023
2+
* Copyright IBM Corp. 2016, 2026
33
*
44
* This source code is licensed under the Apache-2.0 license found in the
55
* LICENSE file in the root directory of this source tree.
@@ -113,6 +113,81 @@ describe('Checkbox', () => {
113113
expect(screen.getByText('Invalid text')).toBeInTheDocument();
114114
});
115115

116+
it('should not respect invalid prop if disabled', () => {
117+
const { container } = render(
118+
<Checkbox
119+
defaultChecked
120+
labelText="Checkbox label"
121+
id="checkbox-label-1"
122+
invalid
123+
disabled
124+
/>
125+
);
126+
127+
// eslint-disable-next-line testing-library/no-node-access, testing-library/no-container
128+
const invalidIcon = container.querySelector(
129+
`svg.${prefix}--checkbox__invalid-icon`
130+
);
131+
132+
expect(screen.getByRole('checkbox')).not.toHaveAttribute('data-invalid');
133+
expect(container.firstChild).not.toHaveClass(
134+
`${prefix}--checkbox-wrapper--invalid`
135+
);
136+
expect(invalidIcon).not.toBeInTheDocument();
137+
});
138+
139+
it('should not respect invalid prop if readOnly', () => {
140+
const { container } = render(
141+
<Checkbox
142+
defaultChecked
143+
labelText="Checkbox label"
144+
id="checkbox-label-1"
145+
invalid
146+
readOnly
147+
/>
148+
);
149+
150+
// eslint-disable-next-line testing-library/no-node-access, testing-library/no-container
151+
const invalidIcon = container.querySelector(
152+
`svg.${prefix}--checkbox__invalid-icon`
153+
);
154+
155+
expect(screen.getByRole('checkbox')).not.toHaveAttribute('data-invalid');
156+
expect(container.firstChild).not.toHaveClass(
157+
`${prefix}--checkbox-wrapper--invalid`
158+
);
159+
expect(invalidIcon).not.toBeInTheDocument();
160+
});
161+
162+
it('should remain disabled when readOnly and disabled are both true', () => {
163+
render(
164+
<Checkbox
165+
defaultChecked
166+
labelText="Checkbox label"
167+
id="checkbox-label-1"
168+
readOnly
169+
disabled
170+
/>
171+
);
172+
173+
expect(screen.getByRole('checkbox')).toBeDisabled();
174+
});
175+
176+
it('should display helperText when invalid is true but disabled', () => {
177+
render(
178+
<Checkbox
179+
defaultChecked
180+
labelText="Checkbox label"
181+
id="checkbox-label-1"
182+
invalid
183+
disabled
184+
helperText="Helper text"
185+
/>
186+
);
187+
188+
expect(screen.getByText('Helper text')).toBeInTheDocument();
189+
});
190+
116191
it('should respect readOnly prop', () => {
117192
const { container } = render(
118193
<Checkbox
@@ -149,6 +224,50 @@ describe('Checkbox', () => {
149224
expect(warnIcon).toBeInTheDocument();
150225
});
151226

227+
it('should not respect warn prop if disabled', () => {
228+
const { container } = render(
229+
<Checkbox
230+
defaultChecked
231+
labelText="Checkbox label"
232+
id="checkbox-label-1"
233+
warn
234+
disabled
235+
/>
236+
);
237+
238+
// eslint-disable-next-line testing-library/no-node-access, testing-library/no-container
239+
const warnIcon = container.querySelector(
240+
`svg.${prefix}--checkbox__invalid-icon--warning`
241+
);
242+
243+
expect(container.firstChild).not.toHaveClass(
244+
`${prefix}--checkbox-wrapper--warning`
245+
);
246+
expect(warnIcon).not.toBeInTheDocument();
247+
});
248+
249+
it('should not respect warn prop if readOnly', () => {
250+
const { container } = render(
251+
<Checkbox
252+
defaultChecked
253+
labelText="Checkbox label"
254+
id="checkbox-label-1"
255+
warn
256+
readOnly
257+
/>
258+
);
259+
260+
// eslint-disable-next-line testing-library/no-node-access, testing-library/no-container
261+
const warnIcon = container.querySelector(
262+
`svg.${prefix}--checkbox__invalid-icon--warning`
263+
);
264+
265+
expect(container.firstChild).not.toHaveClass(
266+
`${prefix}--checkbox-wrapper--warning`
267+
);
268+
expect(warnIcon).not.toBeInTheDocument();
269+
});
270+
152271
it('should display warnText if warn prop is true', () => {
153272
render(
154273
<Checkbox

0 commit comments

Comments
 (0)