1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
5 import React, { useState, useRef, useEffect } from 'react';
6 import { CustomStyleRulesCallback } from 'common/custom-theme';
7 import { WithStyles } from '@mui/styles';
8 import withStyles from '@mui/styles/withStyles';
9 import classnames from 'classnames';
10 import { ArvadosTheme } from 'common/custom-theme';
11 import { OverflowMenu, OverflowChild } from './ms-toolbar-overflow-menu';
13 type CssRules = 'visible' | 'inVisible' | 'toolbarWrapper' | 'overflowStyle';
15 const styles: CustomStyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
18 visibility: 'visible',
24 pointerEvents: 'none',
29 padding: '0 0px 0 20px',
41 children: OverflowChild[];
45 export const IntersectionObserverWrapper = withStyles(styles)((props: WrapperProps & WithStyles<CssRules>) => {
46 const { classes, children, menuLength } = props;
47 const lastEntryId = (children[menuLength - 1] as any).props['data-targetid'];
48 const navRef = useRef<any>(null);
49 const [visibilityMap, setVisibilityMap] = useState<Record<string, boolean>>({});
50 const [numHidden, setNumHidden] = useState(() => findNumHidden(visibilityMap));
51 const prevNumHidden = useRef(numHidden);
53 const handleIntersection = (entries) => {
54 const updatedEntries: Record<string, boolean> = {};
55 entries.forEach((entry) => {
56 const targetid = entry.target.dataset.targetid as string;
57 //if true, the element is visible
58 if (entry.isIntersecting) {
59 updatedEntries[targetid] = true;
61 updatedEntries[targetid] = false;
65 setVisibilityMap((prev) => ({
68 [lastEntryId]: Object.keys(updatedEntries)[0] === lastEntryId,
72 //ensures that the last element is always visible if the second to last is visible
74 if ((prevNumHidden.current > 1 || prevNumHidden.current === 0) && numHidden === 1) {
75 setVisibilityMap((prev) => ({
80 prevNumHidden.current = numHidden;
81 }, [numHidden, lastEntryId]);
84 setNumHidden(findNumHidden(visibilityMap));
87 useEffect((): any => {
89 const observer = new IntersectionObserver(handleIntersection, {
91 rootMargin: '0px -30px 0px 0px',
94 // We are adding observers to child elements of the container div
95 // with ref as navRef. Notice that we are adding observers
96 // only if we have the data attribute targetid on the child element
98 Array.from(navRef.current.children).forEach((item: any) => {
99 if (item.dataset.targetid) {
100 observer.observe(item);
104 observer.disconnect();
106 // eslint-disable-next-line
109 function findNumHidden(visMap: {}) {
110 return Object.values(visMap).filter((x) => x === false).length;
115 className={classes.toolbarWrapper}
118 {React.Children.map(children, (child) => {
119 return React.cloneElement(child, {
120 className: classnames(child.props.className, {
121 [classes.visible]: !!visibilityMap[child.props['data-targetid']],
122 [classes.inVisible]: !visibilityMap[child.props['data-targetid']],
128 visibilityMap={visibilityMap}
129 className={classes.overflowStyle}
131 {children.filter((child) => !child.props['data-targetid'].includes("Divider"))}