Skip to content
Open
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
3f24258
fix(DatePicker): fixed disabled state for DatePicker
suniltc-ibm Mar 17, 2026
b0365d0
fix(DatePicker): using isReadOnly variable
suniltc-ibm Mar 23, 2026
996b0f0
fix(DatePicker): added test case
suniltc-ibm Mar 25, 2026
eee513c
Update packages/react/src/components/DatePicker/DatePicker-test.js
suniltc-ibm Mar 26, 2026
ecc786a
fix(Datepicker): fixed conflicts
suniltc-ibm May 11, 2026
7a3d2f4
Merge branch 'main' into fix/datepicker-read-only
heloiselui May 11, 2026
5ad5dc4
Merge branch 'main' into fix/datepicker-read-only
suniltc-ibm May 12, 2026
aa55ac6
Merge branch 'main' into fix/datepicker-read-only
sangeethababu9223 May 12, 2026
05ca04b
Merge branch 'main' into fix/datepicker-read-only
heloiselui May 12, 2026
2c7ba85
fix(DatePicker): fixed disabled state for web components
suniltc-ibm May 22, 2026
cb78a18
Merge branch 'main' into fix/datepicker-read-only
suniltc-ibm May 23, 2026
5a22f2c
Merge branch 'main' into fix/datepicker-read-only
suniltc-ibm May 27, 2026
db1ca92
Merge branch 'main' into fix/datepicker-read-only
suniltc-ibm Jun 2, 2026
65509f4
fix(DatePicker): updated tests
suniltc-ibm Jun 2, 2026
4d56d03
Merge branch 'carbon-design-system:main' into fix/datepicker-read-only
suniltc-ibm Jun 29, 2026
a377c84
fix(DatePicker): addressed review comments
suniltc-ibm Jun 29, 2026
ec697f2
Merge branch 'main' into fix/datepicker-read-only
suniltc-ibm Jul 9, 2026
4f872f2
Merge branch 'carbon-design-system:main' into fix/datepicker-read-only
suniltc-ibm Jul 25, 2026
3f54880
fix(DatePicker): fixed failing test
suniltc-ibm Jul 25, 2026
603f1a2
Merge branch 'main' into fix/datepicker-read-only
suniltc-ibm Jul 26, 2026
a984e21
Merge branch 'main' into fix/datepicker-read-only
suniltc-ibm Jul 27, 2026
60e38fc
Merge branch 'main' into fix/datepicker-read-only
suniltc-ibm Jul 27, 2026
00ed83b
fix(DatePicker): updated label with disabled check
suniltc-ibm Jul 30, 2026
39b6a31
Merge branch 'main' into fix/datepicker-read-only
suniltc-ibm Jul 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions packages/react/src/components/ComboBox/ComboBox-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1986,4 +1986,22 @@ describe('ComboBox', () => {
expect(findMenuItemNode(i)).toHaveAttribute('aria-selected', 'false');
}
});

it('should prioritize disabled over readOnly when both are true', () => {
render(
<ComboBox
id="combobox"
items={mockProps.items}
itemToString={(item) => (item ? item.text : '')}
titleText="ComboBox label"
disabled={true}
readOnly={true}
/>
);

const input = screen.getByRole('combobox');
expect(input).toHaveAttribute('disabled', '');
expect(input.disabled).toBe(true);
expect(input.readOnly).toBe(false);
});
});
3 changes: 2 additions & 1 deletion packages/react/src/components/ComboBox/ComboBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,7 @@ const ComboBox = forwardRef(

const titleClasses = cx(`${prefix}--label`, {
[`${prefix}--label--disabled`]: disabled,
[`${prefix}--label--readonly`]: readOnly && !disabled,
});
const helperTextId = `combobox-helper-text-${comboBoxInstanceId}`;
const warnTextId = `combobox-warn-text-${comboBoxInstanceId}`;
Expand Down Expand Up @@ -1208,7 +1209,7 @@ const ComboBox = forwardRef(
})}
{...rest}
{...readOnlyEventHandlers}
readOnly={readOnly}
{...(readOnly && !disabled ? { readOnly: true } : {})}
aria-describedby={ariaDescribedBy}
/>

Expand Down
21 changes: 21 additions & 0 deletions packages/react/src/components/DatePicker/DatePicker-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1007,6 +1007,27 @@ describe('Range date picker', () => {
expect(onChange).toHaveBeenCalledTimes(0);
});

it('should prioritize disabled over readOnly on standalone DatePickerInput when both are true', () => {
render(
<DatePickerInput
id="date-picker-input-standalone"
placeholder="mm/dd/yyyy"
labelText="Date Picker label"
disabled={true}
readOnly={true}
/>
);

const input = screen.getByLabelText('Date Picker label');
expect(input).toHaveAttribute('disabled', '');
expect(input.disabled).toBe(true);
expect(input.readOnly).toBe(false);

const label = screen.getByText('Date Picker label');
expect(label).toHaveClass(`${prefix}--label--disabled`);
expect(label).not.toHaveClass(`${prefix}--label--readonly`);
});

it('should work with ISO 8601 format or others', async () => {
const onChange = jest.fn();

Expand Down
6 changes: 4 additions & 2 deletions packages/react/src/components/DatePicker/DatePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -521,10 +521,12 @@ const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>((props, ref) => {
const onHook = (_electedDates, _dateStr, instance) => {
updateClassNames(instance, prefix);
if (startInputField?.current) {
startInputField.current.readOnly = readOnly;
startInputField.current.readOnly =
readOnly && !startInputField.current.disabled;
}
if (endInputField?.current) {
endInputField.current.readOnly = readOnly;
endInputField.current.readOnly =
readOnly && !endInputField.current.disabled;
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,13 +222,14 @@ const DatePickerInput = frFn((props, ref) => {
const labelClasses = cx(`${prefix}--label`, {
[`${prefix}--visually-hidden`]: hideLabel,
[`${prefix}--label--disabled`]: normalizedProps.disabled,
[`${prefix}--label--readonly`]: readOnly,
[`${prefix}--label--readonly`]: readOnly && !normalizedProps.disabled,
});
const helperTextClasses = cx(`${prefix}--form__helper-text`, {
[`${prefix}--form__helper-text--disabled`]: normalizedProps.disabled,
});
const inputClasses = cx(`${prefix}--date-picker__input`, {
[`${prefix}--date-picker__input--${size}`]: size,
[`${prefix}--date-picker__input--disabled`]: normalizedProps.disabled,
[`${prefix}--date-picker__input--invalid`]: normalizedProps.invalid,
[`${prefix}--date-picker__input--warn`]: normalizedProps.warn,
});
Expand Down Expand Up @@ -261,6 +262,10 @@ const DatePickerInput = frFn((props, ref) => {
ref,
['aria-describedby']: ariaDescribedBy,
};

if (readOnly && !normalizedProps.disabled) {
inputProps.readOnly = true;
}
if (normalizedProps.invalid) {
inputProps['data-invalid'] = true;
inputProps['aria-invalid'] = true;
Expand Down
3 changes: 2 additions & 1 deletion packages/react/src/components/Dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -466,14 +466,15 @@ const Dropdown = React.forwardRef(
[`${prefix}--dropdown--inline`]: inline,
[`${prefix}--dropdown--disabled`]: normalizedProps.disabled,
[`${prefix}--dropdown--light`]: light,
[`${prefix}--dropdown--readonly`]: readOnly,
[`${prefix}--dropdown--readonly`]: readOnly && !normalizedProps.disabled,
[`${prefix}--dropdown--${size}`]: size,
[`${prefix}--list-box--up`]: direction === 'top',
[`${prefix}--autoalign`]: autoAlign,
});

const titleClasses = cx(`${prefix}--label`, {
[`${prefix}--label--disabled`]: normalizedProps.disabled,
[`${prefix}--label--readonly`]: readOnly && !normalizedProps.disabled,
[`${prefix}--visually-hidden`]: hideLabel,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -718,4 +718,22 @@ describe('Validation message ids', () => {
'test-dropdown-warn-msg'
);
});

it('should prioritize disabled over readOnly when both are true', () => {
render(
<Dropdown
id="dropdown"
items={mockProps.items}
itemToString={(item) => (item ? item.text : '')}
titleText="Dropdown label"
disabled={true}
readOnly={true}
/>
);

const button = screen.getByRole('combobox');
expect(button).toHaveAttribute('disabled', '');
expect(button.disabled).toBe(true);
expect(button).not.toHaveAttribute('aria-readonly');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,7 @@ export const FilterableMultiSelect = forwardRef(function FilterableMultiSelect<
const titleClasses = cx({
[`${prefix}--label`]: true,
[`${prefix}--label--disabled`]: disabled,
[`${prefix}--label--readonly`]: readOnly && !disabled,
[`${prefix}--visually-hidden`]: hideLabel,
});
const helperClasses = cx({
Expand Down Expand Up @@ -856,7 +857,7 @@ export const FilterableMultiSelect = forwardRef(function FilterableMultiSelect<
[`${prefix}--multi-select--selected`]:
controlledSelectedItems?.length > 0,
[`${prefix}--multi-select--filterable--input-focused`]: inputFocused,
[`${prefix}--multi-select--readonly`]: readOnly,
[`${prefix}--multi-select--readonly`]: readOnly && !disabled,
[`${prefix}--multi-select--selectall`]: selectAll,
}
);
Expand Down Expand Up @@ -1029,7 +1030,7 @@ export const FilterableMultiSelect = forwardRef(function FilterableMultiSelect<
{...inputProp}
ref={mergedRef}
{...readOnlyEventHandlers}
readOnly={readOnly}
{...(readOnly && !disabled ? { readOnly: true } : {})}
/>
{normalizedProps.invalid && (
<WarningFilled className={`${prefix}--list-box__invalid-icon`} />
Expand Down
3 changes: 2 additions & 1 deletion packages/react/src/components/MultiSelect/MultiSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,7 @@ export const MultiSelect = React.forwardRef(
);
const titleClasses = cx(`${prefix}--label`, {
[`${prefix}--label--disabled`]: disabled,
[`${prefix}--label--readonly`]: readOnly,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as earlier.

[`${prefix}--visually-hidden`]: hideLabel,
});
const helperId = !helperText
Expand Down Expand Up @@ -806,7 +807,7 @@ export const MultiSelect = React.forwardRef(
type="button"
className={`${prefix}--list-box__field`}
disabled={disabled}
aria-disabled={disabled || readOnly}
aria-disabled={readOnly ? true : undefined}
aria-describedby={
!inline && showHelperText ? helperId : undefined
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1531,4 +1531,23 @@ describe('FilterableMultiSelect', () => {
.slice(1)
.forEach((opt) => expect(opt).toHaveAttribute('aria-selected', 'false'));
});

it('should prioritize disabled over readOnly when both are true', () => {
const items = generateItems(5, generateGenericItem);
render(
<FilterableMultiSelect
id="filterable-multiselect"
items={items}
itemToString={(item) => (item ? item.label : '')}
titleText="FilterableMultiSelect label"
disabled={true}
readOnly={true}
/>
);

const input = screen.getByRole('combobox');
expect(input).toHaveAttribute('disabled', '');
expect(input.disabled).toBe(true);
expect(input.readOnly).toBe(false);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -1693,4 +1693,22 @@ describe('MultiSelect', () => {
);
});
});

it('should prioritize disabled over readOnly when both are true', () => {
render(
<MultiSelect
id="multiselect"
items={mockProps.items}
itemToString={(item) => (item ? item.text : '')}
titleText="MultiSelect label"
disabled={true}
readOnly={true}
/>
);

const button = screen.getByRole('combobox');
expect(button).toHaveAttribute('disabled', '');
expect(button.disabled).toBe(true);
expect(button).not.toHaveAttribute('aria-readonly');
});
});
11 changes: 8 additions & 3 deletions packages/react/src/components/NumberInput/NumberInput.tsx
Comment thread
adamalston marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -945,6 +945,7 @@ const NumberInput = React.forwardRef<HTMLInputElement, NumberInputProps>(
data-invalid={normalizedProps.invalid ? true : undefined}>
<Label
disabled={normalizedProps.disabled}
readOnly={readOnly}
hideLabel={hideLabel}
id={id}
label={label}
Expand All @@ -955,7 +956,9 @@ const NumberInput = React.forwardRef<HTMLInputElement, NumberInputProps>(
data-invalid={normalizedProps.invalid ? true : undefined}
aria-invalid={normalizedProps.invalid}
aria-describedby={ariaDescribedBy}
aria-readonly={readOnly}
aria-readonly={
readOnly && !normalizedProps.disabled ? true : undefined
}
disabled={normalizedProps.disabled}
ref={ref}
id={id}
Expand Down Expand Up @@ -1066,7 +1069,7 @@ const NumberInput = React.forwardRef<HTMLInputElement, NumberInputProps>(
}}
pattern={pattern}
inputMode={inputMode}
readOnly={readOnly}
{...(readOnly && !disabled ? { readOnly: true } : {})}
step={step}
type={type}
value={type === 'number' ? value : inputValue}
Expand Down Expand Up @@ -1360,16 +1363,18 @@ NumberInput.propTypes = {

interface LabelProps {
disabled?: boolean;
readOnly?: boolean;
hideLabel?: boolean;
id?: string;
label?: ReactNode;
}

const Label = ({ disabled, id, hideLabel, label }: LabelProps) => {
const Label = ({ disabled, readOnly, id, hideLabel, label }: LabelProps) => {
const prefix = usePrefix();
const className = cx({
[`${prefix}--label`]: true,
[`${prefix}--label--disabled`]: disabled,
[`${prefix}--label--readonly`]: readOnly,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as earlier.

[`${prefix}--visually-hidden`]: hideLabel,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2727,6 +2727,22 @@ describe('NumberInput', () => {

numberFormatSpy.mockRestore();
});

it('should prioritize disabled over readOnly when both are true', () => {
render(
<NumberInput
id="input-1"
label="NumberInput label"
disabled={true}
readOnly={true}
/>
);

const input = screen.getByRole('spinbutton');
expect(input).toHaveAttribute('disabled', '');
expect(input.disabled).toBe(true);
expect(input.readOnly).toBe(false);
});
});

describe('validateNumberSeparators - Indian locale', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ const RadioButton = React.forwardRef<HTMLInputElement, RadioButtonProps>(
value={value}
name={name}
required={required}
readOnly={readOnly}
{...(readOnly && !normalizedProps.disabled ? { readOnly: true } : {})}
/>
<label htmlFor={uniqueId} className={`${prefix}--radio-button__label`}>
<span className={`${prefix}--radio-button__appearance`} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,4 +194,21 @@ describe('RadioButton', () => {
);
expect(screen.getByRole('radio')).toHaveAttribute('required');
});

it('should prioritize disabled over readOnly when both are true', () => {
render(
<RadioButton
name="test-name"
value="test-value"
labelText="test-label"
disabled={true}
readOnly={true}
/>
);

const radio = screen.getByRole('radio');
expect(radio).toHaveAttribute('disabled', '');
expect(radio.disabled).toBe(true);
expect(radio.readOnly).toBe(false);
});
});
15 changes: 11 additions & 4 deletions packages/react/src/components/Select/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ const Select = forwardRef<HTMLSelectElement, SelectProps>(
[`${prefix}--select--light`]: light,
[`${prefix}--select--invalid`]: normalizedProps.invalid,
[`${prefix}--select--disabled`]: normalizedProps.disabled,
[`${prefix}--select--readonly`]: readOnly,
[`${prefix}--select--readonly`]: readOnly && !normalizedProps.disabled,
[`${prefix}--select--warning`]: normalizedProps.warn,
[`${prefix}--select--fluid--invalid`]: isFluid && normalizedProps.invalid,
[`${prefix}--select--fluid--focus`]: isFluid && isFocused,
Expand All @@ -228,6 +228,7 @@ const Select = forwardRef<HTMLSelectElement, SelectProps>(
const labelClasses = classNames(`${prefix}--label`, {
[`${prefix}--visually-hidden`]: hideLabel,
[`${prefix}--label--disabled`]: normalizedProps.disabled,
[`${prefix}--label--readonly`]: readOnly && !normalizedProps.disabled,
});
const inputClasses = classNames({
[`${prefix}--select-input`]: true,
Expand Down Expand Up @@ -266,7 +267,7 @@ const Select = forwardRef<HTMLSelectElement, SelectProps>(
const readOnlyEventHandlers = {
onMouseDown: (evt) => {
// NOTE: does not prevent click
if (readOnly) {
if (readOnly && !normalizedProps.disabled) {
evt.preventDefault();
// focus on the element as per readonly input behavior
evt.target.focus();
Expand All @@ -275,7 +276,11 @@ const Select = forwardRef<HTMLSelectElement, SelectProps>(
onKeyDown: (evt) => {
const selectAccessKeys = ['ArrowDown', 'ArrowUp', ' '];
// This prevents the select from opening for the above keys
if (readOnly && selectAccessKeys.includes(evt.key)) {
if (
readOnly &&
!normalizedProps.disabled &&
selectAccessKeys.includes(evt.key)
) {
evt.preventDefault();
}
},
Expand All @@ -298,7 +303,9 @@ const Select = forwardRef<HTMLSelectElement, SelectProps>(
className={inputClasses}
disabled={normalizedProps.disabled || undefined}
aria-invalid={normalizedProps.invalid || undefined}
aria-readonly={readOnly || undefined}
aria-readonly={
readOnly && !normalizedProps.disabled ? true : undefined
}
title={title}
onChange={composeEventHandlers([onChange, handleChange])}
{...readOnlyEventHandlers}
Expand Down
Loading
Loading