Skip to content

Commit e1158fb

Browse files
committed
feat(many): add missing inputRef prop to input components
INSTUI-4400
1 parent 9c23166 commit e1158fb

File tree

12 files changed

+109
-18
lines changed

12 files changed

+109
-18
lines changed

packages/ui-checkbox/src/Checkbox/__new-tests__/Checkbox.test.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,14 @@ describe('<Checkbox />', () => {
105105
expect(inputElem).toHaveAttribute('aria-checked', 'mixed')
106106
})
107107

108+
it('should provide an inputRef prop', async () => {
109+
const inputRef = vi.fn()
110+
renderCheckbox({ inputRef })
111+
const input = screen.getByRole('checkbox')
112+
113+
expect(inputRef).toHaveBeenCalledWith(input)
114+
})
115+
108116
describe('events', () => {
109117
it('when clicked, fires onClick and onChange events', async () => {
110118
const onClick = vi.fn()

packages/ui-checkbox/src/Checkbox/index.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,13 @@ class Checkbox extends Component<CheckboxProps, CheckboxState> {
102102
this.ref = el
103103
}
104104

105+
handleInputRef = (el: HTMLInputElement | null) => {
106+
this._input = el
107+
if (typeof this.props.inputRef === 'function') {
108+
this.props.inputRef(el)
109+
}
110+
}
111+
105112
componentDidMount() {
106113
if (this._input) {
107114
this._input.indeterminate = this.props.indeterminate!
@@ -323,9 +330,7 @@ class Checkbox extends Component<CheckboxProps, CheckboxState> {
323330
id={this.id}
324331
value={value}
325332
type="checkbox"
326-
ref={(c) => {
327-
this._input = c
328-
}}
333+
ref={this.handleInputRef}
329334
required={isRequired}
330335
disabled={disabled || readOnly}
331336
aria-checked={indeterminate ? 'mixed' : undefined}

packages/ui-checkbox/src/Checkbox/props.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ type CheckboxOwnProps = {
8080
inline?: boolean
8181
labelPlacement?: 'top' | 'start' | 'end'
8282
isRequired?: boolean
83+
/**
84+
* A function that provides a reference to the actual underlying input element
85+
*/
86+
inputRef?: (inputElement: HTMLInputElement | null) => void
8387
}
8488

8589
type PropKeys = keyof CheckboxOwnProps
@@ -121,7 +125,8 @@ const propTypes: PropValidators<PropKeys> = {
121125
variant: PropTypes.oneOf(['simple', 'toggle']),
122126
inline: PropTypes.bool,
123127
labelPlacement: PropTypes.oneOf(['top', 'start', 'end']),
124-
isRequired: PropTypes.bool
128+
isRequired: PropTypes.bool,
129+
inputRef: PropTypes.func
125130
}
126131

127132
const allowedProps: AllowedPropKeys = [
@@ -144,7 +149,8 @@ const allowedProps: AllowedPropKeys = [
144149
'variant',
145150
'inline',
146151
'labelPlacement',
147-
'isRequired'
152+
'isRequired',
153+
'inputRef'
148154
]
149155

150156
type CheckboxState = {

packages/ui-color-picker/src/ColorPicker/__new-tests__/ColorPicker.test.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,14 @@ describe('<ColorPicker />', () => {
389389
expect(errorMessage).toBeInTheDocument()
390390
})
391391
})
392+
393+
it('should provide an inputRef prop', async () => {
394+
const inputRef = vi.fn()
395+
render(<SimpleExample inputRef={inputRef} />)
396+
const input = screen.getByRole('textbox')
397+
398+
expect(inputRef).toHaveBeenCalledWith(input)
399+
})
392400
})
393401

394402
describe('complex mode', () => {

packages/ui-color-picker/src/ColorPicker/index.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -608,7 +608,8 @@ class ColorPicker extends Component<ColorPickerProps, ColorPickerState> {
608608
)
609609

610610
render() {
611-
const { disabled, isRequired, placeholderText, width, id } = this.props
611+
const { disabled, isRequired, placeholderText, width, id, inputRef } =
612+
this.props
612613

613614
return (
614615
<div
@@ -633,6 +634,7 @@ class ColorPicker extends Component<ColorPickerProps, ColorPickerState> {
633634
onPaste={(event) => this.handleOnPaste(event)}
634635
onBlur={() => this.handleOnBlur()}
635636
messages={this.renderMessages()}
637+
inputRef={inputRef}
636638
/>
637639
{!this.isSimple && (
638640
<div

packages/ui-color-picker/src/ColorPicker/props.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,11 @@ type ColorPickerOwnProps = {
234234
* Margin around the component. Accepts a `Spacing` token. See token values and example usage in [this guide](https://instructure.design/#layout-spacing).
235235
*/
236236
margin?: Spacing
237+
238+
/**
239+
* A function that provides a reference to the input element
240+
*/
241+
inputRef?: (inputElement: HTMLInputElement | null) => void
237242
}
238243

239244
type ColorPickerState = {
@@ -326,7 +331,8 @@ const propTypes: PropValidators<PropKeys> = {
326331
value: PropTypes.string,
327332
width: PropTypes.string,
328333
withAlpha: PropTypes.bool,
329-
margin: PropTypes.string
334+
margin: PropTypes.string,
335+
inputRef: PropTypes.func
330336
}
331337

332338
const allowedProps: AllowedPropKeys = [
@@ -350,7 +356,8 @@ const allowedProps: AllowedPropKeys = [
350356
'value',
351357
'width',
352358
'withAlpha',
353-
'margin'
359+
'margin',
360+
'inputRef'
354361
]
355362

356363
export type {

packages/ui-radio-input/src/RadioInput/__new-tests__/RadioInput.test.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,21 @@ describe('<RadioInput />', () => {
6161
expect(input).toHaveAttribute('type', 'radio')
6262
})
6363

64+
it('should provide an inputRef prop', async () => {
65+
const inputRef = vi.fn()
66+
const { container } = render(
67+
<RadioInput
68+
label="fake label"
69+
value="someValue"
70+
name="someName"
71+
inputRef={inputRef}
72+
/>
73+
)
74+
const input = container.querySelector('input')
75+
76+
expect(inputRef).toHaveBeenCalledWith(input)
77+
})
78+
6479
describe('events', () => {
6580
it('responds to onClick event', async () => {
6681
const onClick = vi.fn()

packages/ui-radio-input/src/RadioInput/index.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,13 @@ class RadioInput extends Component<RadioInputProps, RadioInputState> {
8484
this.props.makeStyles?.()
8585
}
8686

87+
handleInputRef = (el: HTMLInputElement | null) => {
88+
this._input = el
89+
if (typeof this.props.inputRef === 'function') {
90+
this.props.inputRef(el)
91+
}
92+
}
93+
8794
handleClick: React.MouseEventHandler<HTMLInputElement> = (e) => {
8895
if (this.props.disabled || this.props.readOnly) {
8996
e.preventDefault()
@@ -144,9 +151,7 @@ class RadioInput extends Component<RadioInputProps, RadioInputState> {
144151
<input
145152
{...props}
146153
id={this.id}
147-
ref={(c: HTMLInputElement | null) => {
148-
this._input = c
149-
}}
154+
ref={this.handleInputRef}
150155
value={value}
151156
name={name}
152157
checked={this.checked}

packages/ui-radio-input/src/RadioInput/props.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ type RadioInputOwnProps = {
7272
* event.target.value will contain the new value. It will always be a string.
7373
*/
7474
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
75+
/**
76+
* A function that provides a reference to the actual underlying input element
77+
*/
78+
inputRef?: (inputElement: HTMLInputElement | null) => void
7579
}
7680

7781
type PropKeys = keyof RadioInputOwnProps
@@ -107,7 +111,8 @@ const propTypes: PropValidators<PropKeys> = {
107111
context: PropTypes.oneOf(['success', 'warning', 'danger', 'off']),
108112
inline: PropTypes.bool,
109113
onClick: PropTypes.func,
110-
onChange: PropTypes.func
114+
onChange: PropTypes.func,
115+
inputRef: PropTypes.func
111116
}
112117

113118
const allowedProps: AllowedPropKeys = [
@@ -123,7 +128,8 @@ const allowedProps: AllowedPropKeys = [
123128
'context',
124129
'inline',
125130
'onClick',
126-
'onChange'
131+
'onChange',
132+
'inputRef'
127133
]
128134

129135
export type { RadioInputProps, RadioInputState, RadioInputStyle }

packages/ui-range-input/src/RangeInput/__new-tests__/RangeInput.test.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,23 @@ describe('<RangeInput />', () => {
107107
expect(input).toHaveValue('25')
108108
})
109109

110+
it('should provide an inputRef prop', async () => {
111+
const inputRef = vi.fn()
112+
const { container } = render(
113+
<RangeInput
114+
label="Opacity"
115+
name="opacity"
116+
max={100}
117+
min={0}
118+
defaultValue={42}
119+
inputRef={inputRef}
120+
/>
121+
)
122+
const input = container.querySelector('input')
123+
124+
expect(inputRef).toHaveBeenCalledWith(input)
125+
})
126+
110127
describe('thumbVariant prop', () => {
111128
it('should throw deprecation warning by default', async () => {
112129
render(

0 commit comments

Comments
 (0)