浏览代码

refactor(web): menu positioning

Paul Armstrong 4 年之前
父节点
当前提交
f00628f4e5
共有 4 个文件被更改,包括 32 次插入23 次删除
  1. 1 1
      web/src/components/AppBar.jsx
  2. 4 3
      web/src/components/Menu.jsx
  3. 26 18
      web/src/components/RelativeModal.jsx
  4. 1 1
      web/src/components/Select.jsx

+ 1 - 1
web/src/components/AppBar.jsx

@@ -82,7 +82,7 @@ export default function AppBar({ title }) {
       </div>
       </div>
       <LinkedLogo />
       <LinkedLogo />
       <div className="flex-grow-1 flex justify-end w-full">
       <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">
           <Button color="black" className="rounded-full w-12 h-12" onClick={handleShowMenu} type="text">
             <MoreIcon />
             <MoreIcon />
           </Button>
           </Button>

+ 4 - 3
web/src/components/Menu.jsx

@@ -2,7 +2,7 @@ import { h } from 'preact';
 import RelativeModal from './RelativeModal';
 import RelativeModal from './RelativeModal';
 import { useCallback, useEffect } from 'preact/hooks';
 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 ? (
   return relativeTo ? (
     <RelativeModal
     <RelativeModal
       children={children}
       children={children}
@@ -11,6 +11,7 @@ export default function Menu({ className, children, onDismiss, relativeTo }) {
       onDismiss={onDismiss}
       onDismiss={onDismiss}
       portalRootID="menus"
       portalRootID="menus"
       relativeTo={relativeTo}
       relativeTo={relativeTo}
+      widthRelative={widthRelative}
     />
     />
   ) : null;
   ) : null;
 }
 }
@@ -38,11 +39,11 @@ export function MenuItem({ focus, icon: Icon, label, onSelect, value }) {
       role="option"
       role="option"
     >
     >
       {Icon ? (
       {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 />
           <Icon />
         </div>
         </div>
       ) : null}
       ) : null}
-      {label}
+      <div class="whitespace-nowrap">{label}</div>
     </div>
     </div>
   );
   );
 }
 }

+ 26 - 18
web/src/components/RelativeModal.jsx

@@ -2,10 +2,18 @@ import { h, Fragment } from 'preact';
 import { createPortal } from 'preact/compat';
 import { createPortal } from 'preact/compat';
 import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'preact/hooks';
 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 [show, setShow] = useState(false);
   const portalRoot = portalRootID && document.getElementById(portalRootID);
   const portalRoot = portalRootID && document.getElementById(portalRootID);
   const ref = useRef(null);
   const ref = useRef(null);
@@ -44,12 +52,15 @@ export default function RelativeModal({ className, role = 'dialog', children, on
       const windowWidth = window.innerWidth;
       const windowWidth = window.innerWidth;
       const windowHeight = window.innerHeight;
       const windowHeight = window.innerHeight;
       const { width: menuWidth, height: menuHeight } = ref.current.getBoundingClientRect();
       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 top = y + height;
       let left = x;
       let left = x;
       // too far right
       // 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
       // too far left
       else if (left < WINDOW_PADDING) {
       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;
       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]');
       const focusable = ref.current.querySelector('[tabindex]');
-      focusable && console.log('focusing');
       focusable && focusable.focus();
       focusable && focusable.focus();
     }
     }
-  }, [relativeTo && relativeTo.current, ref && ref.current]);
+  }, [relativeTo && relativeTo.current, ref && ref.current, widthRelative]);
 
 
   useEffect(() => {
   useEffect(() => {
-    if (position.width) {
+    if (position.top >= 0) {
       setShow(true);
       setShow(true);
     } else {
     } else {
       setShow(false);
       setShow(false);
     }
     }
-  }, [show, position.width, ref.current]);
+  }, [show, position.top, ref.current]);
 
 
   const menu = (
   const menu = (
     <Fragment>
     <Fragment>
@@ -91,13 +105,7 @@ export default function RelativeModal({ className, role = 'dialog', children, on
         onkeydown={handleKeydown}
         onkeydown={handleKeydown}
         role={role}
         role={role}
         ref={ref}
         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}
         {children}
       </div>
       </div>

+ 1 - 1
web/src/components/Select.jsx

@@ -95,7 +95,7 @@ export default function Select({ label, onChange, options: inputOptions = [], se
         value={options[selected]?.label}
         value={options[selected]?.label}
       />
       />
       {showMenu ? (
       {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) => (
           {options.map(({ value, label }, i) => (
             <MenuItem key={value} label={label} focus={focused === i} onSelect={handleSelect} value={value} />
             <MenuItem key={value} label={label} focus={focused === i} onSelect={handleSelect} value={value} />
           ))}
           ))}