Morning, all.
I have an issue I have been struggling with for a little while. I have been using react-aria to build a popover in Storybook. This popover is built in two components, the first one is the popover itself, this works fine:
import { StyledPopover } from './PopOver.styles';
import {
DismissButton,
FocusScope,
mergeProps,
useDialog,
useModal,
useOverlay,
} from 'react-aria';
import React, { forwardRef } from 'react';
import { useObjectRef } from '@react-aria/utils';
export interface PopoverProps {
title: string;
children: React.ReactNode;
isOpen: boolean;
onClose: () => void;
}
const Popover = React.forwardRef<HTMLDivElement, PopoverProps>(
({ title, children, isOpen, onClose }, ref) => {
const forwardRef = useObjectRef(ref);
// Handle interacting outside the dialog and pressing
// the Escape key to close the modal.
const { overlayProps } = useOverlay(
{
onClose,
isOpen,
isDismissable: true,
},
forwardRef
);
// Hide content outside the modal from screen readers.
const { modalProps } = useModal();
// Get props for the dialog and its title
const { dialogProps, titleProps } = useDialog({}, forwardRef);
return (
<FocusScope restoreFocus>
<StyledPopover
{...mergeProps(overlayProps, dialogProps, modalProps)}
ref={ref}
>
<h3 {...titleProps} style={{ marginTop: 0 }}>
{title}
</h3>
{children}
<DismissButton onDismiss={onClose} />
</StyledPopover>
</FocusScope>
);
}
);
export { Popover };
Then I have the the button and the state:
import React from 'react';
import {
OverlayContainer,
useOverlayPosition,
useOverlayTrigger,
} from 'react-aria';
import { Button } from '../Button';
import { useOverlayTriggerState } from 'react-stately';
import { Popover } from 'Atoms/Popover/Popover';
export interface PopoverButtonProps {
title: string;
buttonText: string;
children: React.ReactNode;
disabled: boolean;
}
const PopoverButton = ({
buttonText,
title,
children,
disabled,
}: PopoverButtonProps) => {
const state: any = useOverlayTriggerState({});
const triggerRef = React.useRef<HTMLButtonElement>(null);
const overlayRef: any = React.useRef<HTMLDivElement>(null);
// Get props for the trigger and overlay. This also handles
// hiding the overlay when a parent element of the trigger scrolls
// (which invalidates the popover positioning).
const { triggerProps, overlayProps } = useOverlayTrigger(
{ type: 'dialog' },
state,
triggerRef
);
// Get popover positioning props relative to the trigger
const { overlayProps: positionProps } = useOverlayPosition({
targetRef: triggerRef,
overlayRef,
placement: 'top',
offset: 5,
isOpen: state.isOpen,
});
//
// const handleOnClick = (e: any) => {
// console.log(triggerProps);
// triggerProps.onPress && triggerProps.onPress(e);
// };
console.log(triggerProps);
return (
<>
<Button
onClick={(e: React.MouseEvent<HTMLInputElement>) =>
triggerProps.onPress && triggerProps.onPress(e)
}
style="secondary"
size="small"
disabled={disabled}
>
{buttonText}
</Button>
{state.isOpen && (
<OverlayContainer>
<Popover
{...overlayProps}
{...positionProps}
ref={overlayRef}
title={title}
isOpen={state.isOpen}
onClose={state.close}
>
{children}
</Popover>
</OverlayContainer>
)}
</>
);
};
export { PopoverButton };
Now, react-aria useButton takes an onPress, not an onClick.
So, in my Button component I have casted the onClick like this:
import classNames from 'classnames';
import { forwardRef } from 'react';
import { StyledButton } from './Button.styles';
import { useButton } from 'react-aria';
import { useObjectRef } from '@react-aria/utils';
import React from 'react';
export interface ButtonProps
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'style'> {
children: React.ReactNode;
type?: 'submit' | 'button' | 'reset';
style?: 'primary' | 'secondary' | 'icon' | 'text';
size?: 'large' | 'medium' | 'small';
block?: boolean;
disabled?: boolean;
testId?: string;
onPress?: () => void;
}
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
(
{
children,
type,
style = 'primary',
size = 'large',
block = false,
disabled = false,
testId,
onPress,
...props
},
ref
) => {
const classes = classNames(style, `btn-${size}`, {
block,
});
const objRef = useObjectRef(ref);
const { buttonProps } = useButton({ onPress }, objRef);
return (
<StyledButton
{...buttonProps}
className={classes}
onClick={onPress}
type={type}
disabled={disabled}
data-testid={testId}
ref={ref}
{...props}
>
{children}
</StyledButton>
);
}
);
export { Button };
In my popoverButton component, I am passing (e) as it is required for react-aria:
onClick={(e: React.MouseEvent<HTMLInputElement>) =>
triggerProps.onPress && triggerProps.onPress(e)
}
However, I am getting these two errors -
on (e) -
Argument of type 'MouseEventHandler<HTMLButtonElement>' is not assignable to parameter of type 'PressEvent'.
Type 'MouseEventHandler<HTMLButtonElement>' is missing the following properties from type 'PressEvent': type, pointerType, target, shiftKey, and 3 more.ts(2345)
onClick -
Type '(e: React.MouseEventHandler<HTMLButtonElement>) => void | undefined' is not assignable to type 'MouseEventHandler<HTMLButtonElement>'.
Types of parameters 'e' and 'event' are incompatible.
Type 'MouseEvent<HTMLButtonElement, MouseEvent>' is not assignable to type 'MouseEventHandler<HTMLButtonElement>'.
Type 'MouseEvent<HTMLButtonElement, MouseEvent>' provides no match for the signature '(event: MouseEvent<HTMLButtonElement, MouseEvent>): void'.ts(2322)
index.d.ts(1446, 9): The expected type comes from property 'onClick' which is declared here on type 'IntrinsicAttributes & ButtonProps & RefAttributes<HTMLButtonElement>'
Now, the button works and the popup does appear, however it only disappears when clicking the button when it should dismiss when clicking anywhere on the screen. I think this is down to the issues I currently have with the onClick and onPress?
Any ideas?