@chakra-ui/react#useOutsideClick TypeScript Examples

The following examples show how to use @chakra-ui/react#useOutsideClick. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: ContextMenuList.tsx    From wiregui with MIT License 4 votes vote down vote up
ContextMenuList: React.FC<Props & MotionBoxProps> = ({
  children,
  menuId,
  render,
  ...boxProps
}) => {
  const [contextMenusState, setContextMenusState] =
    useRecoilState(contextMenusAtom);

  const menuRef: RefObject<HTMLDivElement> = useRef(null);

  const menu = useMemo(
    () => contextMenusState.menus.find((m) => m.id === menuId),
    [contextMenusState]
  );

  // where should the menu be shown?
  // the ContextMenuTrigger sets this
  const [position, setPosition] = useState<Position>({
    left: -100,
    right: 0,
    top: -100,
    bottom: 0,
  });

  // TODO: Any less manual way to do this
  // figure out where to show the menu
  useEffect(() => {
    let left: number | undefined;
    let right: number | undefined;
    let top: number | undefined;
    let bottom: number | undefined;

    const x = contextMenusState.position.x;
    const y = contextMenusState.position.y;

    const menuHeight = menuRef?.current?.clientHeight;
    const menuWidth = menuRef?.current?.clientWidth;
    const windowWidth = window.innerWidth;
    const windowHeight = window.innerHeight;
    if (!menuHeight || !menuWidth) {
      return;
    }
    if (x + menuWidth > windowWidth) {
      right = windowWidth - x;
    } else {
      left = x;
    }
    if (y + menuHeight > windowHeight) {
      bottom = windowHeight - y;
    } else {
      top = y;
    }
    setPosition({
      top: `${top}px`,
      bottom: `${bottom}px`,
      left: `${left}px`,
      right: `${right}px`,
    });
  }, [menuRef, contextMenusState.position.x, contextMenusState.position.y]);

  // when clicking outside the menu, close it
  useOutsideClick({
    ref: menuRef,
    handler: () => {
      // close all menus
      closeContextMenus();
    },
  });

  const bgColor = useColorModeValue("gray.50", "gray.300");

  const closeContextMenus = () => {
    setContextMenusState((oldState) => {
      return {
        ...oldState,
        position: {
          x: -100,
          y: -100,
        },
        menus: oldState.menus.map((m) => ({
          ...m,
          isOpen: false,
        })),
      };
    });
  };

  return (
    <MotionBox
      variants={motionVariants}
      animate={menu && menu.isOpen ? "enter" : "exit"}
      ref={menuRef}
      borderWidth={1}
      position="fixed"
      bg={bgColor}
      minW={40}
      w={52}
      // maxW={96}
      borderRadius="md"
      display="flex"
      flexDirection="column"
      zIndex="popover"
      {...position}
      {...boxProps}
    >
      {/* either use the render prop or the children */}
      {render
        ? render({
            menuId,
            closeContextMenus,
            passData: contextMenusState.passData,
          })
        : children}
    </MotionBox>
  );
}