fix: click outside select input in popover event bubbling

This commit is contained in:
Tom Moor
2024-01-31 22:40:10 -05:00
parent 8c65e40c7e
commit 05f4fa90b8
3 changed files with 30 additions and 6 deletions

View File

@@ -14,6 +14,7 @@ import Button, { Inner } from "~/components/Button";
import Text from "~/components/Text"; import Text from "~/components/Text";
import useMenuHeight from "~/hooks/useMenuHeight"; import useMenuHeight from "~/hooks/useMenuHeight";
import useMobile from "~/hooks/useMobile"; import useMobile from "~/hooks/useMobile";
import useOnClickOutside from "~/hooks/useOnClickOutside";
import { fadeAndScaleIn } from "~/styles/animations"; import { fadeAndScaleIn } from "~/styles/animations";
import { import {
Position, Position,
@@ -76,9 +77,9 @@ const InputSelect = (props: Props) => {
selectedValue: value, selectedValue: value,
}); });
const popOver = useSelectPopover({ const popover = useSelectPopover({
...select, ...select,
hideOnClickOutside: true, hideOnClickOutside: false,
preventBodyScroll: true, preventBodyScroll: true,
disabled, disabled,
}); });
@@ -107,6 +108,16 @@ const InputSelect = (props: Props) => {
(option) => option.value === select.selectedValue (option) => option.value === select.selectedValue
); );
// Custom click outside handling rather than using `hideOnClickOutside` from reakit so that we can
// prevent event bubbling.
useOnClickOutside(contentRef, (event) => {
if (select.visible) {
event.stopPropagation();
event.preventDefault();
select.hide();
}
});
React.useEffect(() => { React.useEffect(() => {
previousValue.current = value; previousValue.current = value;
select.setSelectedValue(value); select.setSelectedValue(value);
@@ -156,7 +167,7 @@ const InputSelect = (props: Props) => {
</StyledButton> </StyledButton>
)} )}
</Select> </Select>
<SelectPopover {...select} {...popOver} aria-label={ariaLabel}> <SelectPopover {...select} {...popover} aria-label={ariaLabel}>
{(props: InnerProps) => { {(props: InnerProps) => {
const topAnchor = props.style?.top === "0"; const topAnchor = props.style?.top === "0";
const rightAnchor = props.placement === "bottom-end"; const rightAnchor = props.placement === "bottom-end";

View File

@@ -6,6 +6,7 @@ import breakpoint from "styled-components-breakpoint";
import { depths, s } from "@shared/styles"; import { depths, s } from "@shared/styles";
import useKeyDown from "~/hooks/useKeyDown"; import useKeyDown from "~/hooks/useKeyDown";
import useMobile from "~/hooks/useMobile"; import useMobile from "~/hooks/useMobile";
import useOnClickOutside from "~/hooks/useOnClickOutside";
import { fadeAndScaleIn } from "~/styles/animations"; import { fadeAndScaleIn } from "~/styles/animations";
type Props = PopoverProps & { type Props = PopoverProps & {
@@ -29,6 +30,7 @@ const Popover: React.FC<Props> = ({
mobilePosition, mobilePosition,
...rest ...rest
}: Props) => { }: Props) => {
const contentRef = React.useRef<HTMLDivElement>(null);
const isMobile = useMobile(); const isMobile = useMobile();
// Custom Escape handler rather than using hideOnEsc from reakit so we can // Custom Escape handler rather than using hideOnEsc from reakit so we can
@@ -46,6 +48,16 @@ const Popover: React.FC<Props> = ({
} }
); );
// Custom click outside handling rather than using `hideOnClickOutside` from reakit so that we can
// respect event.defaultPrevented.
useOnClickOutside(contentRef, (event) => {
if (rest.visible && !event.defaultPrevented) {
event.stopPropagation();
event.preventDefault();
rest.hide();
}
});
if (isMobile) { if (isMobile) {
return ( return (
<Dialog {...rest} modal> <Dialog {...rest} modal>
@@ -62,8 +74,9 @@ const Popover: React.FC<Props> = ({
} }
return ( return (
<ReakitPopover {...rest} hideOnEsc={false}> <ReakitPopover {...rest} hideOnEsc={false} hideOnClickOutside={false}>
<Contents <Contents
ref={contentRef}
$shrink={shrink} $shrink={shrink}
$width={width} $width={width}
$scrollable={scrollable} $scrollable={scrollable}

View File

@@ -22,6 +22,6 @@ export default function useOnClickOutside(
[ref, callback] [ref, callback]
); );
useEventListener("mousedown", listener); useEventListener("mousedown", listener, window);
useEventListener("touchstart", listener); useEventListener("touchstart", listener, window);
} }