Prepare for switching AccessibleButton and derivatives to forwardRef (#12072)

* Improve AccessibleButton props & docs

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Improve AccessibleTooltipButton props docs

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Simplify roving tab index hook usage

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Ditch RefObject type casts

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Convert AccessibleTooltipButton to a Functional Component

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski 2023-12-20 10:58:24 +00:00 committed by GitHub
parent 2212fbadd0
commit bf61d93bf4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 140 additions and 103 deletions

View file

@ -15,85 +15,105 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { SyntheticEvent, FocusEvent } from "react";
import React, { SyntheticEvent, FocusEvent, useEffect, useState } from "react";
import AccessibleButton from "./AccessibleButton";
import Tooltip, { Alignment } from "./Tooltip";
interface IProps extends React.ComponentProps<typeof AccessibleButton> {
/**
* Type of props accepted by {@link AccessibleTooltipButton}.
*
* Extends that of {@link AccessibleButton}.
*/
interface Props extends React.ComponentProps<typeof AccessibleButton> {
/**
* Title to show in the tooltip and use as aria-label
*/
title?: string;
/**
* Tooltip node to show in the tooltip, takes precedence over `title`
*/
tooltip?: React.ReactNode;
/**
* Trigger label to render
*/
label?: string;
/**
* Classname to apply to the tooltip
*/
tooltipClassName?: string;
/**
* Force the tooltip to be hidden
*/
forceHide?: boolean;
/**
* Alignment to render the tooltip with
*/
alignment?: Alignment;
/**
* Function to call when the children are hovered over
*/
onHover?: (hovering: boolean) => void;
/**
* Function to call when the tooltip goes from shown to hidden.
*/
onHideTooltip?(ev: SyntheticEvent): void;
}
interface IState {
hover: boolean;
}
function AccessibleTooltipButton({
title,
tooltip,
children,
forceHide,
alignment,
onHideTooltip,
tooltipClassName,
...props
}: Props): JSX.Element {
const [hover, setHover] = useState(false);
export default class AccessibleTooltipButton extends React.PureComponent<IProps, IState> {
public constructor(props: IProps) {
super(props);
this.state = {
hover: false,
};
}
public componentDidUpdate(prevProps: Readonly<IProps>): void {
if (!prevProps.forceHide && this.props.forceHide && this.state.hover) {
this.setState({
hover: false,
});
useEffect(() => {
// If forceHide is set then force hover to off to hide the tooltip
if (forceHide && hover) {
setHover(false);
}
}
}, [forceHide, hover]);
private showTooltip = (): void => {
if (this.props.onHover) this.props.onHover(true);
if (this.props.forceHide) return;
this.setState({
hover: true,
});
const showTooltip = (): void => {
props.onHover?.(true);
if (forceHide) return;
setHover(true);
};
private hideTooltip = (ev: SyntheticEvent): void => {
if (this.props.onHover) this.props.onHover(false);
this.setState({
hover: false,
});
this.props.onHideTooltip?.(ev);
const hideTooltip = (ev: SyntheticEvent): void => {
props.onHover?.(false);
setHover(false);
onHideTooltip?.(ev);
};
private onFocus = (ev: FocusEvent): void => {
const onFocus = (ev: FocusEvent): void => {
// We only show the tooltip if focus arrived here from some other
// element, to avoid leaving tooltips hanging around when a modal closes
if (ev.relatedTarget) this.showTooltip();
if (ev.relatedTarget) showTooltip();
};
public render(): React.ReactNode {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { title, tooltip, children, tooltipClassName, forceHide, alignment, onHideTooltip, ...props } =
this.props;
const tip = this.state.hover && (title || tooltip) && (
<Tooltip tooltipClassName={tooltipClassName} label={tooltip || title} alignment={alignment} />
);
return (
<AccessibleButton
{...props}
onMouseOver={this.showTooltip || props.onMouseOver}
onMouseLeave={this.hideTooltip || props.onMouseLeave}
onFocus={this.onFocus || props.onFocus}
onBlur={this.hideTooltip || props.onBlur}
aria-label={title || props["aria-label"]}
>
{children}
{this.props.label}
{(tooltip || title) && tip}
</AccessibleButton>
);
}
const tip = hover && (title || tooltip) && (
<Tooltip tooltipClassName={tooltipClassName} label={tooltip || title} alignment={alignment} />
);
return (
<AccessibleButton
{...props}
onMouseOver={showTooltip}
onMouseLeave={hideTooltip}
onFocus={onFocus}
onBlur={hideTooltip}
aria-label={title || props["aria-label"]}
>
{children}
{props.label}
{(tooltip || title) && tip}
</AccessibleButton>
);
}
export default AccessibleTooltipButton;