Skip to content

Commit

Permalink
Update popup behavior.
Browse files Browse the repository at this point in the history
  • Loading branch information
mikecao committed Oct 17, 2023
1 parent 4d74836 commit 446bb86
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 48 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-basics",
"version": "0.103.0",
"version": "0.104.0",
"description": "Everyday components for React",
"license": "MIT",
"repository": {
Expand Down
1 change: 1 addition & 0 deletions src/components/input/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export function Menu(props: MenuProps) {
} = props;

function handleSelect(key: Key, e: MouseEvent) {
e.stopPropagation();
onSelect?.(key, e);
}

Expand Down
16 changes: 4 additions & 12 deletions src/components/overlay/Popup.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { forwardRef, ReactNode, Ref } from 'react';
import { forwardRef, Ref } from 'react';
import { CommonProps } from 'components/types';
import classNames from 'classnames';
import usePopup from 'hooks/usePopup';
Expand All @@ -8,19 +8,11 @@ import styles from './Popup.module.css';
export interface PopupProps extends CommonProps {
position?: 'top' | 'bottom' | 'left' | 'right';
alignment?: 'start' | 'end' | 'center';
popupElement?: ReactNode;
}

function Popup(props: PopupProps, forwardedRef?: Ref<HTMLDivElement>) {
const { close } = usePopup();
const {
position = 'bottom',
alignment = 'center',
popupElement,
className,
children,
...domProps
} = props;
const { close, wrapperElement } = usePopup();
const { position = 'bottom', alignment = 'center', className, children, ...domProps } = props;

return (
<div
Expand All @@ -29,7 +21,7 @@ function Popup(props: PopupProps, forwardedRef?: Ref<HTMLDivElement>) {
className={classNames(styles.popup, className, styles[position], styles[alignment])}
onClick={e => e.stopPropagation()}
>
{typeof children === 'function' ? children(close, popupElement) : children}
{typeof children === 'function' ? children(close, wrapperElement) : children}
</div>
);
}
Expand Down
85 changes: 55 additions & 30 deletions src/components/trigger/PopupTrigger.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { cloneElement, createContext, EventHandler, useRef, useState } from 'react';
import { cloneElement, createContext, EventHandler, useCallback, useRef, useState } from 'react';
import classNames from 'classnames';
import useDocumentClick from 'hooks/useDocumentClick';
import useKeyDown from 'hooks/useKeyDown';
Expand Down Expand Up @@ -29,42 +29,67 @@ export function PopupTrigger(props: PopupTriggerProps) {
const wrapperRef = useRef<HTMLDivElement>(null);
const clickEnabled = !disabled && action === 'click';
const hoverEnabled = !disabled && action === 'hover';
const [triggerElement, popupElement] = children;

useKeyDown('Escape', () => setShow(false));
console.log({ triggerElement, popupElement });

useDocumentClick(e => {
if (!wrapperRef?.current?.contains(e.target)) {
setShow(false);
onTrigger?.(false, e);
}
});
useKeyDown('Escape', () => setShow(false));

const handleClick = e => {
setShow(state => {
onTrigger?.(!state, e);
return !state;
});
};
useDocumentClick(
useCallback(
e => {
if (show && !wrapperRef?.current?.contains(e.target)) {
setShow(false);
onTrigger?.(false, e);
}
},
[show],
),
);

const handleEnter = e => {
setShow(true);
onTrigger?.(true, e);
};
const handleClick = useCallback(
e => {
setShow(state => {
onTrigger?.(!state, e);
return !state;
});
},
[triggerElement],
);

const handleLeave = e => {
setShow(false);
onTrigger?.(false, e);
};
const handleEnter = useCallback(
e => {
setShow(true);
onTrigger?.(true, e);
},
[triggerElement],
);

const handleClose = e => {
setShow(false);
onTrigger?.(false, e);
};
const handleLeave = useCallback(
e => {
setShow(false);
onTrigger?.(false, e);
},
[triggerElement],
);

const [triggerElement, popupElement] = children;
const handleClose = useCallback(
e => {
setShow(false);
onTrigger?.(false, e);
},
[triggerElement],
);

return (
<PopupContext.Provider value={{ close: handleClose } as any}>
<PopupContext.Provider
value={
{
close: handleClose,
wrapperElement: wrapperRef.current,
} as any
}
>
<div
{...domProps}
ref={wrapperRef}
Expand All @@ -74,12 +99,12 @@ export function PopupTrigger(props: PopupTriggerProps) {
onMouseLeave={hoverEnabled ? handleLeave : undefined}
>
{triggerElement &&
cloneElement(triggerElement as any, {
cloneElement(triggerElement, {
className: classNames(triggerElement.props.className, {
[styles.clickable]: clickEnabled,
}),
})}
{show && cloneElement(popupElement as any, { popupElement: wrapperRef.current })}
{show && popupElement}
</div>
</PopupContext.Provider>
);
Expand Down
6 changes: 3 additions & 3 deletions src/hooks/useDocumentClick.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { useCallback, useEffect } from 'react';

export function useDocumentClick(handler) {
const onClick = useCallback(handler, []);
const onClick = useCallback(handler, [handler]);

useEffect(() => {
document.addEventListener('click', onClick);
document.body.addEventListener('click', onClick);

return () => {
document.removeEventListener('click', onClick);
document.body.removeEventListener('click', onClick);
};
}, [onClick]);

Expand Down
4 changes: 2 additions & 2 deletions src/hooks/usePopup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { useContext } from 'react';
import { PopupContext } from 'components/trigger/PopupTrigger';

export function usePopup() {
const { close } = useContext<any>(PopupContext);
const { close, wrapperElement } = useContext<any>(PopupContext);

return { close };
return { close, wrapperElement };
}

export default usePopup;

0 comments on commit 446bb86

Please sign in to comment.