mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-04-19 22:12:13 +00:00
refactor(useOutsideClick): pass ref as hook param, use ref to calculate context menu offset
This commit is contained in:
parent
a15ce0ea52
commit
e87048799f
4 changed files with 40 additions and 29 deletions
|
|
@ -1,11 +1,11 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
const useOutsideClick = (callback: () => void) => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
import { RefObject, useEffect } from 'react';
|
||||
|
||||
const useOutsideClick = (ref: RefObject<HTMLDivElement>, callback: () => void) => {
|
||||
useEffect(() => {
|
||||
if (!ref?.current) return;
|
||||
|
||||
const handleClickOutside = (event: MouseEvent | TouchEvent) => {
|
||||
if (ref.current && !ref.current.contains(event.target as Node)) {
|
||||
callback();
|
||||
|
|
@ -19,9 +19,7 @@ const useOutsideClick = (callback: () => void) => {
|
|||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
document.removeEventListener('touchstart', handleClickOutside);
|
||||
};
|
||||
}, [callback]);
|
||||
|
||||
return ref;
|
||||
}, [ref, callback]);
|
||||
};
|
||||
|
||||
export default useOutsideClick;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Copyright (C) 2017-2024 Smart code 203358507
|
||||
|
||||
import React from 'react';
|
||||
import React, { useRef } from 'react';
|
||||
import { Button } from 'stremio/components';
|
||||
import useBinaryState from 'stremio/common/useBinaryState';
|
||||
import Dropdown from './Dropdown';
|
||||
|
|
@ -19,13 +19,15 @@ type Props = {
|
|||
|
||||
const MultiselectMenu = ({ className, title, options, selectedOption, onSelect }: Props) => {
|
||||
const [menuOpen, , closeMenu, toggleMenu] = useBinaryState(false);
|
||||
const multiselectMenuRef = useOutsideClick(() => closeMenu());
|
||||
const multiselectMenuRef = useRef(null);
|
||||
const [level, setLevel] = React.useState<number>(0);
|
||||
|
||||
const onOptionSelect = (value: number) => {
|
||||
level ? setLevel(level + 1) : onSelect(value), closeMenu();
|
||||
};
|
||||
|
||||
useOutsideClick(multiselectMenuRef, closeMenu);
|
||||
|
||||
return (
|
||||
<div className={classNames(styles['multiselect-menu'], className)} ref={multiselectMenuRef}>
|
||||
<Button
|
||||
|
|
|
|||
|
|
@ -10,10 +10,7 @@ const { useServices } = require('stremio/services');
|
|||
const Option = require('./Option');
|
||||
const styles = require('./styles');
|
||||
|
||||
const OptionsMenu = ({ className, stream, playbackDevices, style, onOutsideClick }) => {
|
||||
const ref = useOutsideClick(() => {
|
||||
if (typeof onOutsideClick === 'function') onOutsideClick();
|
||||
});
|
||||
const OptionsMenu = ({ menuRef, className, stream, playbackDevices, style, onOutsideClick }) => {
|
||||
const { t } = useTranslation();
|
||||
const { core } = useServices();
|
||||
const platform = usePlatform();
|
||||
|
|
@ -73,8 +70,13 @@ const OptionsMenu = ({ className, stream, playbackDevices, style, onOutsideClick
|
|||
const onMouseDown = React.useCallback((event) => {
|
||||
event.nativeEvent.optionsMenuClosePrevented = true;
|
||||
}, []);
|
||||
|
||||
useOutsideClick(menuRef, () => {
|
||||
if (typeof onOutsideClick === 'function') onOutsideClick();
|
||||
});
|
||||
|
||||
return (
|
||||
<div ref={ref} style={style} className={classnames(className, styles['options-menu-container'])} onMouseDown={onMouseDown}>
|
||||
<div ref={menuRef} style={style} className={classnames(className, styles['options-menu-container'])} onMouseDown={onMouseDown}>
|
||||
{
|
||||
streamingUrl || downloadUrl ?
|
||||
<Option
|
||||
|
|
@ -114,6 +116,10 @@ const OptionsMenu = ({ className, stream, playbackDevices, style, onOutsideClick
|
|||
};
|
||||
|
||||
OptionsMenu.propTypes = {
|
||||
menuRef: PropTypes.oneOfType([
|
||||
PropTypes.func,
|
||||
PropTypes.shape({ current: PropTypes.any })
|
||||
]),
|
||||
className: PropTypes.string,
|
||||
stream: PropTypes.object,
|
||||
playbackDevices: PropTypes.array,
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
const [immersed, setImmersed] = React.useState(true);
|
||||
const setImmersedDebounced = React.useCallback(debounce(setImmersed, 3000), []);
|
||||
const [, , , toggleFullscreen] = useFullscreen();
|
||||
const [screenWidth, screenHeight] = React.useMemo(() => [window.innerWidth, window.innerHeight], [window]);
|
||||
|
||||
const [optionsMenuOpen, , closeOptionsMenu, toggleOptionsMenu] = useBinaryState(false);
|
||||
const [subtitlesMenuOpen, , closeSubtitlesMenu, toggleSubtitlesMenu] = useBinaryState(false);
|
||||
|
|
@ -64,9 +65,10 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
const [sideDrawerOpen, , closeSideDrawer, toggleSideDrawer] = useBinaryState(false);
|
||||
const [contextMenuOpen, openContextMenu, closeContextMenu] = useBinaryState(false);
|
||||
const [contextCoords, setContextCoords] = React.useState({
|
||||
x: 0,
|
||||
y: 0,
|
||||
x: screenWidth,
|
||||
y: screenHeight,
|
||||
});
|
||||
const contextMenuRef = React.useRef(null);
|
||||
|
||||
const menusOpen = React.useMemo(() => {
|
||||
return optionsMenuOpen || subtitlesMenuOpen || audioMenuOpen || speedMenuOpen || statisticsMenuOpen || sideDrawerOpen || contextMenuOpen;
|
||||
|
|
@ -245,25 +247,27 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
|
||||
const onContextMenu = React.useCallback((e) => {
|
||||
e.preventDefault();
|
||||
let baseFontSize = 14;
|
||||
const { clientX, clientY } = event;
|
||||
const { innerWidth, innerHeight } = window;
|
||||
const { clientX, clientY } = e;
|
||||
|
||||
if (innerWidth > 1600) baseFontSize = 15;
|
||||
if (innerWidth > 2200) baseFontSize = 16;
|
||||
|
||||
const menuWidth = 16 * baseFontSize;
|
||||
const minMenuHeight = 9 * baseFontSize;
|
||||
|
||||
const adjustedX = clientX + menuWidth > innerWidth ? clientX - menuWidth : clientX;
|
||||
const adjustedY = clientY + minMenuHeight > innerHeight ? clientY - minMenuHeight : clientY;
|
||||
const menuSize = contextMenuRef?.current?.getBoundingClientRect();
|
||||
const adjustedX = clientX + menuSize.width > screenWidth ? clientX - menuSize.width : clientX;
|
||||
const adjustedY = clientY + menuSize.height > screenHeight ? clientY - menuSize.height : clientY;
|
||||
|
||||
setContextCoords({
|
||||
x: adjustedX,
|
||||
y: adjustedY,
|
||||
});
|
||||
openContextMenu();
|
||||
}, []);
|
||||
}, [window, contextMenuRef]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!contextMenuOpen) {
|
||||
setContextCoords({
|
||||
x: screenWidth,
|
||||
y: screenHeight
|
||||
});
|
||||
}
|
||||
}, [contextMenuOpen]);
|
||||
|
||||
const onContainerMouseDown = React.useCallback((event) => {
|
||||
if (!event.nativeEvent.optionsMenuClosePrevented) {
|
||||
|
|
@ -690,8 +694,9 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
null
|
||||
}
|
||||
{
|
||||
contextMenuOpen ?
|
||||
player.selected?.stream ?
|
||||
<OptionsMenu
|
||||
menuRef={contextMenuRef}
|
||||
style={
|
||||
{
|
||||
top: `${contextCoords.y}px`,
|
||||
|
|
|
|||
Loading…
Reference in a new issue