21317: fixed collapse occlusion Arvados-DCO-1.1-Signed-off-by: Lisa Knox <lisa.knox...
[arvados.git] / services / workbench2 / src / components / multiselect-toolbar / ms-toolbar-overflow-wrapper.tsx
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import React, { useState, useRef, useEffect } from 'react';
6 import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core';
7 import classnames from 'classnames';
8 import { ArvadosTheme } from 'common/custom-theme';
9 import { OverflowMenu, OverflowChild } from './ms-toolbar-overflow-menu';
10
11 type CssRules = 'visible' | 'inVisible' | 'toolbarWrapper' | 'overflowStyle';
12
13 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
14     visible: {
15         order: 0,
16         visibility: 'visible',
17         opacity: 1,
18     },
19     inVisible: {
20         order: 100,
21         visibility: 'hidden',
22         pointerEvents: 'none',
23     },
24     toolbarWrapper: {
25         display: 'flex',
26         overflow: 'hidden',
27         padding: '0 20px',
28         width: '100%',
29     },
30     overflowStyle: {
31         order: 99,
32         position: 'sticky',
33         right: '-2rem',
34     },
35 });
36
37 type WrapperProps = {
38     children: OverflowChild[];
39     menuLength: number;
40 };
41
42 export const IntersectionObserverWrapper = withStyles(styles)((props: WrapperProps & WithStyles<CssRules>) => {
43     const { classes, children, menuLength } = props;
44     const lastEntryId = (children[menuLength - 1] as any).props['data-targetid'];
45     const navRef = useRef<any>(null);
46     const [visibilityMap, setVisibilityMap] = useState({});
47
48     const handleIntersection = (entries) => {
49         const updatedEntries = {};
50         entries.forEach((entry) => {
51             const targetid = entry.target.dataset.targetid;
52             if (entry.isIntersecting) {
53                 updatedEntries[targetid] = true;
54             } else {
55                 updatedEntries[targetid] = false;
56             }
57         });
58
59         setVisibilityMap((prev) => ({
60                 ...prev,
61                 ...updatedEntries,
62                 [lastEntryId]: Object.keys(updatedEntries)[0] === lastEntryId,
63             })
64         );
65     };
66
67     useEffect((): any => {
68         setVisibilityMap({})
69         const observer = new IntersectionObserver(handleIntersection, {
70             root: navRef.current,
71             rootMargin: '0px -30px 0px 0px',
72             threshold: 1,
73         });
74         // We are adding observers to child elements of the container div
75         // with ref as navRef. Notice that we are adding observers
76         // only if we have the data attribute targetid on the child element
77         if (navRef.current)
78             Array.from(navRef.current.children).forEach((item: any) => {
79                 if (item.dataset.targetid) {
80                     observer.observe(item);
81                 }
82             });
83         return () => {
84             observer.disconnect();
85         };
86         // eslint-disable-next-line 
87     }, [menuLength]);
88
89     const numHidden = (visMap: {}) => {
90         return Object.values(visMap).filter((x) => x === false).length;
91     };
92
93     return (
94         <div
95             className={classes.toolbarWrapper}
96             ref={navRef}
97         >
98             {React.Children.map(children, (child) => {
99                 return React.cloneElement(child, {
100                     className: classnames(child.props.className, {
101                         [classes.visible]: !!visibilityMap[child.props['data-targetid']],
102                         [classes.inVisible]: !visibilityMap[child.props['data-targetid']],
103                     }),
104                 });
105             })}
106             {numHidden(visibilityMap) >= 2 && (
107                 <OverflowMenu
108                     visibilityMap={visibilityMap}
109                     className={classes.overflowStyle}
110                 >
111                     {children}
112                 </OverflowMenu>
113             )}
114         </div>
115     );
116 });