diff --git a/packages/libs/coreui/src/components/buttons/PopoverButton/PopoverButton.tsx b/packages/libs/coreui/src/components/buttons/PopoverButton/PopoverButton.tsx index 3dfe53f36c..63cb4dbf04 100644 --- a/packages/libs/coreui/src/components/buttons/PopoverButton/PopoverButton.tsx +++ b/packages/libs/coreui/src/components/buttons/PopoverButton/PopoverButton.tsx @@ -1,4 +1,11 @@ -import { ReactNode, useState, useEffect, useMemo } from 'react'; +import { + ReactNode, + useState, + useEffect, + useMemo, + forwardRef, + useImperativeHandle, +} from 'react'; import { Popover } from '@material-ui/core'; import SwissArmyButton from '../SwissArmyButton'; import { gray } from '../../../definitions/colors'; @@ -66,6 +73,11 @@ const defaultStyle: ButtonStyleSpec = { }, }; +export interface PopoverButtonHandle { + /** Closes the popover */ + close: () => void; +} + export interface PopoverButtonProps { /** Contents of the menu when opened */ children: ReactNode; @@ -87,86 +99,96 @@ export interface PopoverButtonProps { /** * Renders a button that display `children` in a popover widget. */ -export default function PopoverButton(props: PopoverButtonProps) { - const { - children, - buttonDisplayContent, - onClose, - setIsPopoverOpen, - isDisabled = false, - styleOverrides = {}, - } = props; - const [anchorEl, setAnchorEl] = useState(null); - - const finalStyle = useMemo( - () => merge({}, defaultStyle, styleOverrides), - [styleOverrides] - ); - - const onCloseHandler = () => { - setAnchorEl(null); - onClose && onClose(); - }; - - useEffect(() => { - if (!setIsPopoverOpen) return; - if (anchorEl) { - setIsPopoverOpen(true); - } else { - setIsPopoverOpen(false); - } - }, [anchorEl, setIsPopoverOpen]); - - const menu = ( - - {children} - - ); - - const button = ( - setAnchorEl(event.currentTarget)} - disabled={isDisabled} - styleSpec={finalStyle} - icon={ArrowDown} - iconPosition="right" - additionalAriaProperties={{ - 'aria-controls': 'dropdown', - 'aria-haspopup': 'true', - }} - /> - ); - - return ( -
{ - // prevent click event from propagating to ancestor nodes - event.stopPropagation(); - }} - > - {button} - {menu} -
- ); -} + +const PopoverButton = forwardRef( + function PopoverButton(props, ref) { + const { + children, + buttonDisplayContent, + onClose, + setIsPopoverOpen, + isDisabled = false, + styleOverrides = {}, + } = props; + const [anchorEl, setAnchorEl] = useState(null); + + const finalStyle = useMemo( + () => merge({}, defaultStyle, styleOverrides), + [styleOverrides] + ); + + const onCloseHandler = () => { + setAnchorEl(null); + onClose && onClose(); + }; + + // Expose the `close()` method to external components via ref + useImperativeHandle(ref, () => ({ + close: onCloseHandler, + })); + + useEffect(() => { + if (!setIsPopoverOpen) return; + if (anchorEl) { + setIsPopoverOpen(true); + } else { + setIsPopoverOpen(false); + } + }, [anchorEl, setIsPopoverOpen]); + + const menu = ( + + {children} + + ); + + const button = ( + setAnchorEl(event.currentTarget)} + disabled={isDisabled} + styleSpec={finalStyle} + icon={ArrowDown} + iconPosition="right" + additionalAriaProperties={{ + 'aria-controls': 'dropdown', + 'aria-haspopup': 'true', + }} + /> + ); + + return ( +
{ + // prevent click event from propagating to ancestor nodes + event.stopPropagation(); + }} + > + {button} + {menu} +
+ ); + } +); + +export default PopoverButton; diff --git a/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx b/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx index c76e0e8fac..b584b3a12a 100644 --- a/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx +++ b/packages/sites/ortho-site/webapp/wdkCustomization/js/client/records/Sequences.tsx @@ -3,6 +3,7 @@ import React, { useCallback, useDeferredValue, useMemo, + useRef, useState, } from 'react'; import TreeTable from '@veupathdb/components/lib/components/tidytree/TreeTable'; @@ -23,7 +24,9 @@ import { PfamDomainArchitecture } from 'ortho-client/components/pfam-domains/Pfa import { extractPfamDomain } from 'ortho-client/records/utils'; import Banner from '@veupathdb/coreui/lib/components/banners/Banner'; import { RowCounter } from '@veupathdb/coreui/lib/components/Mesa'; -import PopoverButton from '@veupathdb/coreui/lib/components/buttons/PopoverButton/PopoverButton'; +import PopoverButton, { + PopoverButtonHandle, +} from '@veupathdb/coreui/lib/components/buttons/PopoverButton/PopoverButton'; import { PfamDomain } from 'ortho-client/components/pfam-domains/PfamDomain'; import { FilledButton, @@ -384,6 +387,10 @@ export function RecordTable_Sequences( [finalNewick, treeWidth, highlightColor, highlightedNodes] ); + const proteinFilterButtonRef = useRef(null); + + // None shall pass! (hooks, at least) + if ( !mesaState || !sortedRows || @@ -505,24 +512,26 @@ export function RecordTable_Sequences( { + proteinFilterButtonRef.current?.close(); setProteinFilterIds([]); }} /> ); const updateProteinFilterIds = () => { + proteinFilterButtonRef.current?.close(); setProteinFilterIds(highlightedNodes); setHighlightedNodes([]); }; const proteinFilter = ( 0 ? ` (${volatileProteinFilterIds.length})` : '' }${highlightedNodes.length > 0 ? '*' : ''}`} - key={volatileProteinFilterIds.join(':')} >