|
import React, { useState } from 'react'; |
|
import { useFocusVisibleListener } from '@react-aria/interactions'; |
|
import type { ButtonProps as HTMLButtonProps } from 'react-html-props'; |
|
|
|
export interface AccessibleButtonProps extends HTMLButtonProps { |
|
/** |
|
* Whether the button should lose it's focus when the mouse |
|
* is released |
|
* @default false |
|
*/ |
|
unfocusOnMouseUp?: boolean; |
|
/** |
|
* Whether the button should lose it's focus when the mouse |
|
* leaves the area of the actual button |
|
* @default false |
|
*/ |
|
unfocusOnMouseLeave?: boolean; |
|
/** |
|
* The class to apply on focus visible |
|
* @default "focus-ring" |
|
*/ |
|
focusRingClass?: string; |
|
} |
|
|
|
/** |
|
* An unstyled button element that has accessibility built in. |
|
* On focus visible, it applies the `focus-ring` class, as defined in |
|
* index.css, and also removes any transitions |
|
*/ |
|
export const AccessibleButton: React.FC<AccessibleButtonProps> = ({ |
|
onMouseUp, |
|
onMouseLeave, |
|
className, |
|
unfocusOnMouseUp = false, |
|
unfocusOnMouseLeave = false, |
|
focusRingClass = 'focus-ring', |
|
...props |
|
}) => { |
|
// react-aria does have a useFocusVisible hook but the problem |
|
// with that is that it does not work without JS (bad if you |
|
// are using something like Next.js), and the initial value is |
|
// true, when I personally want it to be set by default to false |
|
const [isFocusVisible, setIsFocusVisible] = useState(false); |
|
|
|
// Triggered when focus visible changes to either true or false |
|
useFocusVisibleListener((isFocusVisibleResult) => { |
|
setIsFocusVisible(isFocusVisibleResult); |
|
}, []); |
|
|
|
return ( |
|
<button |
|
className={className + " " + focusRingClass} |
|
style={isFocusVisible ? { transition: 'none' } : {}} |
|
onMouseUp={ |
|
unfocusOnMouseUp |
|
? (e) => { |
|
if (!isFocusVisible) { |
|
e.currentTarget.blur(); |
|
} |
|
onMouseUp?.(e); |
|
} |
|
: onMouseUp |
|
} |
|
onMouseLeave={ |
|
unfocusOnMouseLeave |
|
? (e) => { |
|
if (!isFocusVisible) { |
|
e.currentTarget.blur(); |
|
} |
|
onMouseLeave?.(e); |
|
} |
|
: onMouseLeave |
|
} |
|
{...props} |
|
/> |
|
); |
|
}; |