Last active
June 18, 2023 10:29
-
-
Save tkrotoff/885d0440fd46ea4d3d4919b3914a5223 to your computer and use it in GitHub Desktop.
I want an error tooltip on a button when clicked, let's use the browser native input validation error message
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { forwardRef } from 'react'; | |
// ref is optional with forwardRef(), I want it to be mandatory | |
// type ... = ReturnType<typeof forwardRef<T, P>>; | |
type ForwardMandatoryRefComponent<T, P> = React.ForwardRefExoticComponent< | |
React.PropsWithoutRef<P> & React.RefAttributes<T> & { ref: React.Ref<T> } | |
>; | |
type Props = { | |
// Why? https://stackoverflow.com/a/25367640 | |
parentBorderWidth: number; | |
}; | |
/** | |
* What a nice hack! | |
* I want an error tooltip on a button when clicked, | |
* let's use the browser native input validation error message instead of re-inventing my own. | |
* | |
* Parent should have "position: relative", no need for a form element. | |
* | |
* Example: | |
* | |
* ``` | |
* <Link | |
* href={...} | |
* onClick={e => { | |
* e.preventDefault(); | |
* | |
* const inputTooltip = inputTooltipRef.current!; | |
* inputTooltip.setCustomValidity('Message to display inside the browser native tooltip'); | |
* inputTooltip.reportValidity(); | |
* }} | |
* className="position-relative btn btn-primary" | |
* > | |
* My link text | |
* <FormValidationErrorMessageHack ref={inputTooltipRef} parentBorderWidth={1} /> | |
* </Link> | |
* ``` | |
* | |
* FIXME [Firefox Mobile Android does not show form validation errors](https://github.com/mozilla-mobile/fenix/issues/27986) | |
* | |
* https://twitter.com/tkrotoff/status/1670378044471533571 | |
* https://gist.github.com/tkrotoff/885d0440fd46ea4d3d4919b3914a5223 | |
*/ | |
export const FormValidationErrorMessageHack: ForwardMandatoryRefComponent<HTMLInputElement, Props> = | |
forwardRef<HTMLInputElement, Props>(function FormValidationErrorMessageHack( | |
{ parentBorderWidth }, | |
ref | |
) { | |
return ( | |
<> | |
{/* Avoid Lighthouse accessibility issue "Form elements do not have associated labels" */} | |
<label htmlFor="FormValidationErrorMessageHack" style={{ visibility: 'hidden', height: 0 }}> | |
{/* https://www.fileformat.info/info/unicode/char/200b/index.htm */} | |
​ | |
</label> | |
<input | |
id="FormValidationErrorMessageHack" | |
ref={ref} | |
aria-hidden="true" | |
// No virtual keyboard | |
inputMode="none" | |
// Prevent the input to gain focus | |
tabIndex={-1} | |
style={{ | |
position: 'absolute', | |
// Center the input over parent | |
left: -parentBorderWidth, | |
top: -parentBorderWidth, | |
width: `calc(100% + ${parentBorderWidth}px * 2)`, | |
height: `calc(100% + ${parentBorderWidth}px * 2)`, | |
// Not working with Firefox 111: tooltip is not displayed | |
//visibility: 'hidden', | |
opacity: 0, | |
pointerEvents: 'none' | |
}} | |
/> | |
</> | |
); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { render, screen } from '@testing-library/react'; | |
import { useRef } from 'react'; | |
import { FormValidationErrorMessageHack } from './FormValidationErrorMessageHack'; | |
test('display an HTML error message', () => { | |
function ButtonWithTooltip() { | |
const inputTooltipRef = useRef<HTMLInputElement>(null); | |
return ( | |
<button | |
type="button" | |
onClick={e => { | |
e.preventDefault(); | |
const inputTooltip = inputTooltipRef.current!; | |
inputTooltip.setCustomValidity('My tooltip message'); | |
inputTooltip.reportValidity(); | |
}} | |
style={{ position: 'relative' }} | |
> | |
My button text | |
<FormValidationErrorMessageHack ref={inputTooltipRef} parentBorderWidth={1} /> | |
</button> | |
); | |
} | |
render(<ButtonWithTooltip />); | |
expect(screen.queryByText('My tooltip message')).toBeNull(); | |
const inputValidation = screen.getByLabelText<HTMLInputElement>('\u200B'); | |
expect(inputValidation.validity.valid).toEqual(true); | |
expect(inputValidation.validationMessage).toEqual(''); | |
const button = screen.getByRole<HTMLButtonElement>('button', { name: 'My button text' }); | |
button.click(); | |
// FIXME Does not work with jsdom & Happy DOM | |
//screen.getByText('My tooltip message'); | |
expect(inputValidation.validity.valid).toEqual(false); | |
expect(inputValidation.validationMessage).toEqual('My tooltip message'); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment