-
Notifications
You must be signed in to change notification settings - Fork 321
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add autoFocus to RadioButton #2057
Changes from 3 commits
659d0e6
036a608
890bcf7
c517add
3df1500
0c52132
6384410
bf8ab59
15d56cc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
import cx from "classnames"; | ||
import React, { forwardRef, useCallback, useMemo, useRef } from "react"; | ||
import React, { forwardRef, useCallback, useEffect, useMemo, useRef } from "react"; | ||
import useMergeRef from "../../hooks/useMergeRef"; | ||
import Clickable from "../Clickable/Clickable"; | ||
import Text from "../Text/Text"; | ||
|
@@ -27,6 +27,8 @@ export interface RadioButtonProps extends VibeComponentProps { | |
value?: string; | ||
/** A string specifying a name for the input control. This name is submitted along with the control's value when the form data is submitted. */ | ||
name?: string; | ||
/** is autoFocus */ | ||
autoFocus?: boolean; | ||
/** is disabled */ | ||
disabled?: boolean; | ||
/** why the input is disabled */ | ||
|
@@ -64,6 +66,7 @@ const RadioButton: VibeComponent<RadioButtonProps, HTMLElement> & object = forwa | |
*/ | ||
radioButtonClassName, | ||
disabled = false, | ||
autoFocus = false, | ||
disabledReason, | ||
defaultChecked = false, | ||
children, | ||
|
@@ -80,6 +83,16 @@ const RadioButton: VibeComponent<RadioButtonProps, HTMLElement> & object = forwa | |
const inputRef = useRef<HTMLInputElement | null>(); | ||
const mergedRef = useMergeRef(ref, inputRef); | ||
const overrideClassName = backwardCompatibilityForProperties([className, componentClassName]); | ||
|
||
useEffect(() => { | ||
if (!inputRef?.current || !autoFocus) { | ||
return; | ||
} | ||
|
||
const animationFrame = requestAnimationFrame(() => inputRef.current.focus()); | ||
return () => cancelAnimationFrame(animationFrame); | ||
}, [inputRef, autoFocus]); | ||
|
||
const onChildClick = useCallback(() => { | ||
if (disabled || !retainChildClick) return; | ||
if (inputRef.current) { | ||
|
@@ -114,6 +127,7 @@ const RadioButton: VibeComponent<RadioButtonProps, HTMLElement> & object = forwa | |
type="radio" | ||
value={value} | ||
name={name} | ||
autoFocus={autoFocus} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure if both methods are needed, the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Both should work but IMO it's better to just use the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree, and it does work on Storybook, but they might implemented the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @chensara let's do the following
if it works for you with the native approach, amazing. if not, try the same with the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So I first implemented using only There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. when I've checked with the
chensara marked this conversation as resolved.
Show resolved
Hide resolved
|
||
disabled={disabled} | ||
{...checkedProps} | ||
onChange={onSelect} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,76 +1,99 @@ | ||
import React from "react"; | ||
import "@testing-library/jest-dom"; | ||
import { fireEvent, render, cleanup, screen } from "@testing-library/react"; | ||
import RadioButton from "../RadioButton"; | ||
|
||
describe("RadioButton tests", () => { | ||
const formName = "myForm"; | ||
const radiosName = "radios"; | ||
|
||
const option1Value = "1"; | ||
let onChangeMock1: jest.Mock; | ||
let onChangeMock2: jest.Mock; | ||
let onChangeMock3: jest.Mock; | ||
|
||
const option1Text = "Option 1"; | ||
const option2Value = "2"; | ||
const option2Text = "Option 2"; | ||
const option3Value = "3"; | ||
const option3Text = "Option 3"; | ||
|
||
let onChangeMock1: jest.Mock; | ||
let onChangeMock2: jest.Mock; | ||
let onChangeMock3: jest.Mock; | ||
describe("With one of the radio buttons is checked by default", () => { | ||
beforeEach(() => { | ||
const radiosName = "radios"; | ||
|
||
beforeEach(() => { | ||
onChangeMock1 = jest.fn(); | ||
onChangeMock2 = jest.fn(); | ||
onChangeMock3 = jest.fn(); | ||
const option1Value = "1"; | ||
const option2Value = "2"; | ||
const option3Value = "3"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure about inserting the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
|
||
render( | ||
<form name={formName}> | ||
<RadioButton | ||
name={radiosName} | ||
value={option1Value} | ||
text={option1Text} | ||
onSelect={onChangeMock1} | ||
defaultChecked={true} | ||
/> | ||
<RadioButton name={radiosName} value={option2Value} text={option2Text} onSelect={onChangeMock2} /> | ||
<RadioButton name={radiosName} value={option3Value} text={option3Text} onSelect={onChangeMock3} /> | ||
</form> | ||
); | ||
}); | ||
onChangeMock1 = jest.fn(); | ||
onChangeMock2 = jest.fn(); | ||
onChangeMock3 = jest.fn(); | ||
|
||
afterEach(() => { | ||
cleanup(); | ||
}); | ||
render( | ||
<form name={formName}> | ||
<RadioButton | ||
name={radiosName} | ||
value={option1Value} | ||
text={option1Text} | ||
onSelect={onChangeMock1} | ||
defaultChecked | ||
/> | ||
<RadioButton name={radiosName} value={option2Value} text={option2Text} onSelect={onChangeMock2} /> | ||
<RadioButton name={radiosName} value={option3Value} text={option3Text} onSelect={onChangeMock3} /> | ||
</form> | ||
); | ||
}); | ||
|
||
it("should default select 1st option", () => { | ||
const option1: HTMLInputElement = screen.getByLabelText(option1Text); | ||
const option2: HTMLInputElement = screen.getByLabelText(option2Text); | ||
const option3: HTMLInputElement = screen.getByLabelText(option3Text); | ||
expect(option1.checked).toBeTruthy(); | ||
expect(option2.checked).toBeFalsy(); | ||
expect(option3.checked).toBeFalsy(); | ||
}); | ||
afterEach(() => { | ||
cleanup(); | ||
}); | ||
|
||
it("should select 2nd option", () => { | ||
const option1: HTMLInputElement = screen.getByLabelText(option1Text); | ||
const option2: HTMLInputElement = screen.getByLabelText(option2Text); | ||
const option3: HTMLInputElement = screen.getByLabelText(option3Text); | ||
fireEvent.click(option2); | ||
expect(option1.checked).toBeFalsy(); | ||
expect(option2.checked).toBeTruthy(); | ||
expect(option3.checked).toBeFalsy(); | ||
}); | ||
it("should default select 1st option", () => { | ||
const option1: HTMLInputElement = screen.getByLabelText(option1Text); | ||
const option2: HTMLInputElement = screen.getByLabelText(option2Text); | ||
const option3: HTMLInputElement = screen.getByLabelText(option3Text); | ||
expect(option1.checked).toBeTruthy(); | ||
expect(option2.checked).toBeFalsy(); | ||
expect(option3.checked).toBeFalsy(); | ||
}); | ||
|
||
it("should call the onChange callback when clicked", () => { | ||
const option2 = screen.getByLabelText(option2Text); | ||
fireEvent.click(option2); | ||
expect(onChangeMock2.mock.calls.length).toBe(1); | ||
expect(onChangeMock2.mock.calls[0]).toBeTruthy(); | ||
it("should select 2nd option", () => { | ||
const option1: HTMLInputElement = screen.getByLabelText(option1Text); | ||
const option2: HTMLInputElement = screen.getByLabelText(option2Text); | ||
const option3: HTMLInputElement = screen.getByLabelText(option3Text); | ||
fireEvent.click(option2); | ||
expect(option1.checked).toBeFalsy(); | ||
expect(option2.checked).toBeTruthy(); | ||
expect(option3.checked).toBeFalsy(); | ||
}); | ||
|
||
it("should call the onChange callback when clicked", () => { | ||
const option2 = screen.getByLabelText(option2Text); | ||
fireEvent.click(option2); | ||
expect(onChangeMock2.mock.calls.length).toBe(1); | ||
expect(onChangeMock2.mock.calls[0]).toBeTruthy(); | ||
}); | ||
|
||
it("should be the same text", () => { | ||
const text = "test text"; | ||
const { getByText } = render(<RadioButton text={text} />); | ||
const radioButtonComponentText = getByText(text); | ||
expect(radioButtonComponentText).toBeTruthy(); | ||
}); | ||
}); | ||
|
||
it("should be the same text", () => { | ||
const text = "test text"; | ||
const { getByText } = render(<RadioButton text={text} />); | ||
const radioButtonComponentText = getByText(text); | ||
expect(radioButtonComponentText).toBeTruthy(); | ||
describe("When all radio buttons are unchecked", () => { | ||
it("should auto focus 2nd option", () => { | ||
render( | ||
<form name={formName}> | ||
<RadioButton text={option1Text} /> | ||
<RadioButton text={option2Text} autoFocus /> | ||
<RadioButton text={option3Text} /> | ||
</form> | ||
); | ||
|
||
const option1: HTMLInputElement = screen.getByLabelText(option1Text); | ||
const option2: HTMLInputElement = screen.getByLabelText(option2Text); | ||
const option3: HTMLInputElement = screen.getByLabelText(option3Text); | ||
expect(option1).not.toHaveFocus(); | ||
expect(option2).toHaveFocus(); | ||
expect(option3).not.toHaveFocus(); | ||
}); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe leave it
undefined
in case it isn't suppliedso current snapshots won't add
autoFocus=false
to their snapshots, as it can lead to - when upgrading a version in monolith - to multiple snapshots being brokenwe're ok with it as
undefined
so just remove the default
false
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed :)