mirror of
				https://github.com/blakeblackshear/frigate.git
				synced 2025-10-27 10:52:11 +01:00 
			
		
		
		
	refactor(web): menu positioning
This commit is contained in:
		
							parent
							
								
									19bd5ace7d
								
							
						
					
					
						commit
						f00628f4e5
					
				@ -82,7 +82,7 @@ export default function AppBar({ title }) {
 | 
			
		||||
      </div>
 | 
			
		||||
      <LinkedLogo />
 | 
			
		||||
      <div className="flex-grow-1 flex justify-end w-full">
 | 
			
		||||
        <div ref={moreRef}>
 | 
			
		||||
        <div className="w-auto" ref={moreRef}>
 | 
			
		||||
          <Button color="black" className="rounded-full w-12 h-12" onClick={handleShowMenu} type="text">
 | 
			
		||||
            <MoreIcon />
 | 
			
		||||
          </Button>
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,7 @@ import { h } from 'preact';
 | 
			
		||||
import RelativeModal from './RelativeModal';
 | 
			
		||||
import { useCallback, useEffect } from 'preact/hooks';
 | 
			
		||||
 | 
			
		||||
export default function Menu({ className, children, onDismiss, relativeTo }) {
 | 
			
		||||
export default function Menu({ className, children, onDismiss, relativeTo, widthRelative }) {
 | 
			
		||||
  return relativeTo ? (
 | 
			
		||||
    <RelativeModal
 | 
			
		||||
      children={children}
 | 
			
		||||
@ -11,6 +11,7 @@ export default function Menu({ className, children, onDismiss, relativeTo }) {
 | 
			
		||||
      onDismiss={onDismiss}
 | 
			
		||||
      portalRootID="menus"
 | 
			
		||||
      relativeTo={relativeTo}
 | 
			
		||||
      widthRelative={widthRelative}
 | 
			
		||||
    />
 | 
			
		||||
  ) : null;
 | 
			
		||||
}
 | 
			
		||||
@ -38,11 +39,11 @@ export function MenuItem({ focus, icon: Icon, label, onSelect, value }) {
 | 
			
		||||
      role="option"
 | 
			
		||||
    >
 | 
			
		||||
      {Icon ? (
 | 
			
		||||
        <div className="w-6 h-6 self-center mr-4 text-gray-500">
 | 
			
		||||
        <div className="w-6 h-6 self-center mr-4 text-gray-500 flex-shrink-0">
 | 
			
		||||
          <Icon />
 | 
			
		||||
        </div>
 | 
			
		||||
      ) : null}
 | 
			
		||||
      {label}
 | 
			
		||||
      <div class="whitespace-nowrap">{label}</div>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -2,10 +2,18 @@ import { h, Fragment } from 'preact';
 | 
			
		||||
import { createPortal } from 'preact/compat';
 | 
			
		||||
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'preact/hooks';
 | 
			
		||||
 | 
			
		||||
const WINDOW_PADDING = 20;
 | 
			
		||||
const WINDOW_PADDING = 10;
 | 
			
		||||
 | 
			
		||||
export default function RelativeModal({ className, role = 'dialog', children, onDismiss, portalRootID, relativeTo }) {
 | 
			
		||||
  const [position, setPosition] = useState({ top: -999, left: 0, width: 0 });
 | 
			
		||||
export default function RelativeModal({
 | 
			
		||||
  className,
 | 
			
		||||
  role = 'dialog',
 | 
			
		||||
  children,
 | 
			
		||||
  onDismiss,
 | 
			
		||||
  portalRootID,
 | 
			
		||||
  relativeTo,
 | 
			
		||||
  widthRelative = false,
 | 
			
		||||
}) {
 | 
			
		||||
  const [position, setPosition] = useState({ top: -999, left: -999 });
 | 
			
		||||
  const [show, setShow] = useState(false);
 | 
			
		||||
  const portalRoot = portalRootID && document.getElementById(portalRootID);
 | 
			
		||||
  const ref = useRef(null);
 | 
			
		||||
@ -44,12 +52,15 @@ export default function RelativeModal({ className, role = 'dialog', children, on
 | 
			
		||||
      const windowWidth = window.innerWidth;
 | 
			
		||||
      const windowHeight = window.innerHeight;
 | 
			
		||||
      const { width: menuWidth, height: menuHeight } = ref.current.getBoundingClientRect();
 | 
			
		||||
      const { x, y, width, height } = relativeTo.current.getBoundingClientRect();
 | 
			
		||||
      const { x, y, width: relativeWidth, height } = relativeTo.current.getBoundingClientRect();
 | 
			
		||||
 | 
			
		||||
      const width = widthRelative ? relativeWidth : menuWidth;
 | 
			
		||||
 | 
			
		||||
      let top = y + height;
 | 
			
		||||
      let left = x;
 | 
			
		||||
      // too far right
 | 
			
		||||
      if (left + menuWidth >= windowWidth - WINDOW_PADDING) {
 | 
			
		||||
        left = windowWidth - menuWidth - WINDOW_PADDING;
 | 
			
		||||
      if (left + width >= windowWidth - WINDOW_PADDING) {
 | 
			
		||||
        left = windowWidth - width - WINDOW_PADDING;
 | 
			
		||||
      }
 | 
			
		||||
      // too far left
 | 
			
		||||
      else if (left < WINDOW_PADDING) {
 | 
			
		||||
@ -65,20 +76,23 @@ export default function RelativeModal({ className, role = 'dialog', children, on
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const maxHeight = windowHeight - WINDOW_PADDING * 2 > menuHeight ? null : windowHeight - WINDOW_PADDING * 2;
 | 
			
		||||
      setPosition({ left, top: top + window.scrollY, width, height: maxHeight });
 | 
			
		||||
      const newPosition = { left: left + window.scrollX, top: top + window.scrollY, maxHeight };
 | 
			
		||||
      if (widthRelative) {
 | 
			
		||||
        newPosition.width = relativeWidth;
 | 
			
		||||
      }
 | 
			
		||||
      setPosition(newPosition);
 | 
			
		||||
      const focusable = ref.current.querySelector('[tabindex]');
 | 
			
		||||
      focusable && console.log('focusing');
 | 
			
		||||
      focusable && focusable.focus();
 | 
			
		||||
    }
 | 
			
		||||
  }, [relativeTo && relativeTo.current, ref && ref.current]);
 | 
			
		||||
  }, [relativeTo && relativeTo.current, ref && ref.current, widthRelative]);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (position.width) {
 | 
			
		||||
    if (position.top >= 0) {
 | 
			
		||||
      setShow(true);
 | 
			
		||||
    } else {
 | 
			
		||||
      setShow(false);
 | 
			
		||||
    }
 | 
			
		||||
  }, [show, position.width, ref.current]);
 | 
			
		||||
  }, [show, position.top, ref.current]);
 | 
			
		||||
 | 
			
		||||
  const menu = (
 | 
			
		||||
    <Fragment>
 | 
			
		||||
@ -91,13 +105,7 @@ export default function RelativeModal({ className, role = 'dialog', children, on
 | 
			
		||||
        onkeydown={handleKeydown}
 | 
			
		||||
        role={role}
 | 
			
		||||
        ref={ref}
 | 
			
		||||
        style={
 | 
			
		||||
          position.width > 0
 | 
			
		||||
            ? `min-width: ${position.width}px; ${position.height ? `max-height: ${position.height}px;` : ''} top: ${
 | 
			
		||||
                position.top
 | 
			
		||||
              }px; left: ${position.left}px`
 | 
			
		||||
            : ''
 | 
			
		||||
        }
 | 
			
		||||
        style={position.top >= 0 ? position : null}
 | 
			
		||||
      >
 | 
			
		||||
        {children}
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
@ -95,7 +95,7 @@ export default function Select({ label, onChange, options: inputOptions = [], se
 | 
			
		||||
        value={options[selected]?.label}
 | 
			
		||||
      />
 | 
			
		||||
      {showMenu ? (
 | 
			
		||||
        <Menu className="rounded-t-none" onDismiss={handleDismiss} relativeTo={ref}>
 | 
			
		||||
        <Menu className="rounded-t-none" onDismiss={handleDismiss} relativeTo={ref} widthRelative>
 | 
			
		||||
          {options.map(({ value, label }, i) => (
 | 
			
		||||
            <MenuItem key={value} label={label} focus={focused === i} onSelect={handleSelect} value={value} />
 | 
			
		||||
          ))}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user