Skip to content

Commit 3194ace

Browse files
authored
feat: Add isReadOnly render prop to ComboBox and NumberField (adobe#9892)
1 parent 848684e commit 3194ace

File tree

4 files changed

+46
-7
lines changed

4 files changed

+46
-7
lines changed

packages/react-aria-components/src/ComboBox.tsx

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,12 @@ export interface ComboBoxRenderProps {
6868
* Whether the combobox is required.
6969
* @selector [data-required]
7070
*/
71-
isRequired: boolean
71+
isRequired: boolean,
72+
/**
73+
* Whether the combobox is read only.
74+
* @selector [data-readonly]
75+
*/
76+
isReadOnly: boolean
7277
}
7378

7479
export interface ComboBoxProps<T extends object, M extends SelectionMode = 'single'> extends Omit<AriaComboBoxProps<T, M>, 'children' | 'placeholder' | 'label' | 'description' | 'errorMessage' | 'validationState' | 'validationBehavior'>, RACValidation, RenderProps<ComboBoxRenderProps>, SlotProps, GlobalDOMAttributes<HTMLDivElement> {
@@ -97,7 +102,7 @@ export const ComboBoxStateContext = createContext<ComboBoxState<any, SelectionMo
97102
*/
98103
export const ComboBox = /*#__PURE__*/ (forwardRef as forwardRefType)(function ComboBox<T extends object, M extends SelectionMode = 'single'>(props: ComboBoxProps<T, M>, ref: ForwardedRef<HTMLDivElement>) {
99104
[props, ref] = useContextProps(props, ref, ComboBoxContext);
100-
let {children, isDisabled = false, isInvalid = false, isRequired = false} = props;
105+
let {children, isDisabled = false, isInvalid = false, isRequired = false, isReadOnly = false} = props;
101106
let content = useMemo(() => (
102107
<ListBoxContext.Provider value={{items: props.items ?? props.defaultItems}}>
103108
{typeof children === 'function'
@@ -106,11 +111,12 @@ export const ComboBox = /*#__PURE__*/ (forwardRef as forwardRefType)(function Co
106111
isDisabled,
107112
isInvalid,
108113
isRequired,
109-
defaultChildren: null
114+
defaultChildren: null,
115+
isReadOnly
110116
})
111117
: children}
112118
</ListBoxContext.Provider>
113-
), [children, isDisabled, isInvalid, isRequired, props.items, props.defaultItems]);
119+
), [children, isDisabled, isInvalid, isRequired, isReadOnly, props.items, props.defaultItems]);
114120

115121
return (
116122
<CollectionBuilder content={content}>
@@ -200,8 +206,9 @@ function ComboBoxInner<T extends object>({props, collection, comboBoxRef: ref}:
200206
isOpen: state.isOpen,
201207
isDisabled: props.isDisabled || false,
202208
isInvalid: validation.isInvalid || false,
203-
isRequired: props.isRequired || false
204-
}), [state.isOpen, props.isDisabled, validation.isInvalid, props.isRequired]);
209+
isRequired: props.isRequired || false,
210+
isReadOnly: props.isReadOnly || false
211+
}), [state.isOpen, props.isDisabled, validation.isInvalid, props.isRequired, props.isReadOnly]);
205212

206213
let renderProps = useRenderProps({
207214
...props,
@@ -262,6 +269,7 @@ function ComboBoxInner<T extends object>({props, collection, comboBoxRef: ref}:
262269
data-focused={state.isFocused || undefined}
263270
data-open={state.isOpen || undefined}
264271
data-disabled={props.isDisabled || undefined}
272+
data-readonly={props.isReadOnly || undefined}
265273
data-invalid={validation.isInvalid || undefined}
266274
data-required={props.isRequired || undefined}>
267275
{renderProps.children}

packages/react-aria-components/src/NumberField.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ export interface NumberFieldRenderProps {
5050
* @selector [data-invalid]
5151
*/
5252
isInvalid: boolean,
53+
/**
54+
* Whether the number field is read only.
55+
* @selector [data-readonly]
56+
*/
57+
isReadOnly: boolean,
5358
/**
5459
* Whether the number field is required.
5560
* @selector [data-required]
@@ -111,7 +116,8 @@ export const NumberField = /*#__PURE__*/ (forwardRef as forwardRefType)(function
111116
state,
112117
isDisabled: props.isDisabled || false,
113118
isInvalid: validation.isInvalid || false,
114-
isRequired: props.isRequired || false
119+
isRequired: props.isRequired || false,
120+
isReadOnly: props.isReadOnly || false
115121
},
116122
defaultClassName: 'react-aria-NumberField'
117123
});
@@ -146,6 +152,7 @@ export const NumberField = /*#__PURE__*/ (forwardRef as forwardRefType)(function
146152
ref={ref}
147153
slot={props.slot || undefined}
148154
data-disabled={props.isDisabled || undefined}
155+
data-readonly={props.isReadOnly || undefined}
149156
data-required={props.isRequired || undefined}
150157
data-invalid={validation.isInvalid || undefined} />
151158
{props.name && <input type="hidden" name={props.name} form={props.form} value={isNaN(state.numberValue) ? '' : state.numberValue} disabled={props.isDisabled || undefined} />}

packages/react-aria-components/test/ComboBox.test.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -937,4 +937,16 @@ describe('ComboBox', () => {
937937
expect(comboboxTester.combobox).toHaveFocus();
938938
expect(onOpenChange).toHaveBeenCalledTimes(1);
939939
});
940+
941+
it('should support read-only state', async () => {
942+
let {getByRole, rerender} = render(
943+
<TestComboBox />
944+
);
945+
946+
let input = getByRole('combobox');
947+
948+
expect(input.closest('.react-aria-ComboBox')).not.toHaveAttribute('data-readonly');
949+
rerender(<TestComboBox isReadOnly />);
950+
expect(input.closest('.react-aria-ComboBox')).toHaveAttribute('data-readonly');
951+
});
940952
});

packages/react-aria-components/test/NumberField.test.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,18 @@ describe('NumberField', () => {
120120
expect(group).not.toHaveClass('focus');
121121
});
122122

123+
it('should support read-only state', async () => {
124+
let {getByRole, rerender} = render(
125+
<TestNumberField />
126+
);
127+
128+
let input = getByRole('textbox');
129+
130+
expect(input.closest('.react-aria-NumberField')).not.toHaveAttribute('data-readonly');
131+
rerender(<TestNumberField isReadOnly />);
132+
expect(input.closest('.react-aria-NumberField')).toHaveAttribute('data-readonly');
133+
});
134+
123135
it('should support render props', () => {
124136
let {getByRole} = render(
125137
<NumberField defaultValue={1024} minValue={300} maxValue={1400}>

0 commit comments

Comments
 (0)