Merge branch '21448-menu-reorder' into 21224-project-details
[arvados.git] / services / workbench2 / src / components / multiselect-toolbar / ms-toolbar-overflow-wrapper.tsx
index e63225b05f20aa6f8ab795bfc5175721cc870c52..5c1c433712a718230b2476604e90bd31be49ff5e 100644 (file)
@@ -4,9 +4,9 @@
 
 import React, { useState, useRef, useEffect } from 'react';
 import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core';
-import classnames from 'classnames'
+import classnames from 'classnames';
 import { ArvadosTheme } from 'common/custom-theme';
-import { OverflowMenu } from './ms-toolbar-overflow-menu';
+import { OverflowMenu, OverflowChild } from './ms-toolbar-overflow-menu';
 
 type CssRules = 'visible' | 'inVisible' | 'toolbarWrapper' | 'overflowStyle';
 
@@ -24,27 +24,35 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     toolbarWrapper: {
         display: 'flex',
         overflow: 'hidden',
-        padding: '0 20px',
+        padding: '0 0px 0 20px',
         width: '100%',
     },
     overflowStyle: {
         order: 99,
         position: 'sticky',
         right: '-2rem',
-        backgroundColor: 'white',
+        width: 0,
     },
 });
 
-export const IntersectionObserverWrapper = withStyles(styles)((props: any & WithStyles<CssRules>) => {
-  const { classes, children} = props
+type WrapperProps = {
+    children: OverflowChild[];
+    menuLength: number;
+};
 
+export const IntersectionObserverWrapper = withStyles(styles)((props: WrapperProps & WithStyles<CssRules>) => {
+    const { classes, children, menuLength } = props;
+    const lastEntryId = (children[menuLength - 1] as any).props['data-targetid'];
     const navRef = useRef<any>(null);
-    const [visibilityMap, setVisibilityMap] = useState({});
-
+    const [visibilityMap, setVisibilityMap] = useState<Record<string, boolean>>({});
+    const [numHidden, setNumHidden] = useState(() => findNumHidden(visibilityMap));
+    const prevNumHidden = useRef(numHidden);
+    
     const handleIntersection = (entries) => {
-        const updatedEntries = {};
+        const updatedEntries: Record<string, boolean> = {};
         entries.forEach((entry) => {
-            const targetid = entry.target.dataset.targetid;
+            const targetid = entry.target.dataset.targetid as string;
+            //if true, the element is visible
             if (entry.isIntersecting) {
                 updatedEntries[targetid] = true;
             } else {
@@ -55,12 +63,30 @@ export const IntersectionObserverWrapper = withStyles(styles)((props: any & With
         setVisibilityMap((prev) => ({
             ...prev,
             ...updatedEntries,
+            [lastEntryId]: Object.keys(updatedEntries)[0] === lastEntryId,
         }));
     };
+
+    //ensures that the last element is always visible if the second to last is visible
+    useEffect(() => {
+        if ((prevNumHidden.current > 1 || prevNumHidden.current === 0) && numHidden === 1) {
+            setVisibilityMap((prev) => ({
+                ...prev,
+                [lastEntryId]: true,
+            }));
+        }
+        prevNumHidden.current = numHidden;
+    }, [numHidden, lastEntryId]);
+
+    useEffect(() => {
+        setNumHidden(findNumHidden(visibilityMap));
+    }, [visibilityMap]);
+
     useEffect((): any => {
+        setVisibilityMap({});
         const observer = new IntersectionObserver(handleIntersection, {
             root: navRef.current,
-            rootMargin: '0px -20px 0px 0px',
+            rootMargin: '0px -30px 0px 0px',
             threshold: 1,
         });
         // We are adding observers to child elements of the container div
@@ -75,24 +101,34 @@ export const IntersectionObserverWrapper = withStyles(styles)((props: any & With
         return () => {
             observer.disconnect();
         };
-    }, []);
+        // eslint-disable-next-line
+    }, [menuLength]);
+
+    function findNumHidden(visMap: {}) {
+        return Object.values(visMap).filter((x) => x === false).length;
+    }
 
     return (
-      <div className={classes.toolbarWrapper} ref={navRef}>
-      {React.Children.map(children, (child) => {
-        return React.cloneElement(child, {
-          className: classnames(child.props.className, {
-            [classes.visible]: !!visibilityMap[child.props["data-targetid"]],
-            [classes.inVisible]: !visibilityMap[child.props["data-targetid"]]
-          })
-        });
-      })}
-      <OverflowMenu
-        visibilityMap={visibilityMap}
-        className={classes.overflowStyle}
-      >
-        {children}
-      </OverflowMenu>
-    </div>
+        <div
+            className={classes.toolbarWrapper}
+            ref={navRef}
+        >
+            {React.Children.map(children, (child) => {
+                return React.cloneElement(child, {
+                    className: classnames(child.props.className, {
+                        [classes.visible]: !!visibilityMap[child.props['data-targetid']],
+                        [classes.inVisible]: !visibilityMap[child.props['data-targetid']],
+                    }),
+                });
+            })}
+            {numHidden >= 2 && (
+                <OverflowMenu
+                    visibilityMap={visibilityMap}
+                    className={classes.overflowStyle}
+                >
+                    {children.filter((child) => !child.props['data-targetid'].includes("Divider"))}
+                </OverflowMenu>
+            )}
+        </div>
     );
 });