From d48ed04157dbec0954b7782e7d3a4d9565e45b70 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 5 Jun 2026 06:31:53 +0000 Subject: [PATCH 1/2] fix(search-filters): prevent nested filter dropdowns from disappearing on reopen In Mantine v9, the Accordion Panel uses a Collapse component that transitions through display:none state. NestedFilterGroup previously used {isExpanded && ...} to conditionally mount its content inside the Panel. This caused the virtualizer's scroll element to be inside a display:none container when reopening, resulting in zero dimensions from getBoundingClientRect(). With no viewport size, the virtualizer rendered zero items, making the dropdown appear empty. The fix removes the conditional mount guard. The virtualizer already uses count: isExpanded ? childFilters.length : 0 to control rendering, so no items are rendered when collapsed. The Accordion Panel's built-in Collapse animation handles visibility. When reopening, the scroll element is already in the DOM, allowing the virtualizer's ResizeObserver to detect dimension changes and render items correctly. Fixes: HDX-4471 Co-authored-by: Mike Shi --- .../DBSearchPageFilters/NestedFilterGroup.tsx | 209 +++++++++--------- .../__tests__/DBSearchPageFilters.test.tsx | 23 ++ 2 files changed, 124 insertions(+), 108 deletions(-) diff --git a/packages/app/src/components/DBSearchPageFilters/NestedFilterGroup.tsx b/packages/app/src/components/DBSearchPageFilters/NestedFilterGroup.tsx index 8a3106b5cc..945b9d60e5 100644 --- a/packages/app/src/components/DBSearchPageFilters/NestedFilterGroup.tsx +++ b/packages/app/src/components/DBSearchPageFilters/NestedFilterGroup.tsx @@ -149,122 +149,115 @@ export const NestedFilterGroup = ({ pb={0} pl="xs" > - {isExpanded && ( -
- {childFilters.length === 0 ? ( - - - No properties found - - - ) : ( +
+ {childFilters.length === 0 ? ( + + + No properties found + + + ) : ( +
-
- {rowVirtualizer.getVirtualItems().map(virtualRow => { - const child = childFilters[virtualRow.index]; - const childSelectedValues = selectedValues[ - child.key - ] || { - included: new Set(), - excluded: new Set(), - }; - const childHasSelections = - childSelectedValues.included.size + - childSelectedValues.excluded.size > - 0; + {rowVirtualizer.getVirtualItems().map(virtualRow => { + const child = childFilters[virtualRow.index]; + const childSelectedValues = selectedValues[child.key] || { + included: new Set(), + excluded: new Set(), + }; + const childHasSelections = + childSelectedValues.included.size + + childSelectedValues.excluded.size > + 0; - return ( -
+ ({ + value: value, + label: value.toString(), + }))} + optionsLoading={false} + selectedValues={childSelectedValues} + onChange={value => onChange(child.key, value)} + onClearClick={() => onClearClick(child.key)} + onOnlyClick={value => onOnlyClick(child.key, value)} + onExcludeClick={value => + onExcludeClick(child.key, value) + } + valuePins={{ + onPinClick: value => onPinClick(child.key, value), + isPinned: value => isPinned(child.key, value), + onSharedPinClick: onSharedPinClick + ? value => onSharedPinClick(child.key, value) + : undefined, + isSharedPinned: isSharedPinned + ? value => isSharedPinned(child.key, value) + : undefined, }} - > - ({ - value: value, - label: value.toString(), - }))} - optionsLoading={false} - selectedValues={childSelectedValues} - onChange={value => onChange(child.key, value)} - onClearClick={() => onClearClick(child.key)} - onOnlyClick={value => - onOnlyClick(child.key, value) - } - onExcludeClick={value => - onExcludeClick(child.key, value) - } - valuePins={{ - onPinClick: value => - onPinClick(child.key, value), - isPinned: value => isPinned(child.key, value), - onSharedPinClick: onSharedPinClick - ? value => onSharedPinClick(child.key, value) - : undefined, - isSharedPinned: isSharedPinned - ? value => isSharedPinned(child.key, value) - : undefined, - }} - fieldPins={{ - onFieldPinClick: () => - onFieldPinClick?.(child.key), - isFieldPinned: isFieldPinned?.(child.key), - onToggleSharedFieldPin: () => - onToggleSharedFieldPin?.(child.key), - isSharedFieldPinned: isSharedFieldPinned?.( - child.key, - ), - }} - onColumnToggle={ - onColumnToggle - ? () => onColumnToggle(child.key) - : undefined - } - isColumnDisplayed={displayedColumns?.includes( + fieldPins={{ + onFieldPinClick: () => + onFieldPinClick?.(child.key), + isFieldPinned: isFieldPinned?.(child.key), + onToggleSharedFieldPin: () => + onToggleSharedFieldPin?.(child.key), + isSharedFieldPinned: isSharedFieldPinned?.( child.key, - )} - onLoadMore={() => onLoadMore(child.key)} - loadMoreLoading={ - loadMoreLoading[child.key] || false - } - hasLoadedMore={hasLoadedMore[child.key] || false} - showFilterCounts={showFilterCounts} - isDefaultExpanded={childHasSelections} - chartConfig={chartConfig} - isLive={isLive} - /> -
- ); - })} -
+ ), + }} + onColumnToggle={ + onColumnToggle + ? () => onColumnToggle(child.key) + : undefined + } + isColumnDisplayed={displayedColumns?.includes( + child.key, + )} + onLoadMore={() => onLoadMore(child.key)} + loadMoreLoading={ + loadMoreLoading[child.key] || false + } + hasLoadedMore={hasLoadedMore[child.key] || false} + showFilterCounts={showFilterCounts} + isDefaultExpanded={childHasSelections} + chartConfig={chartConfig} + isLive={isLive} + /> +
+ ); + })}
- )} -
- )} +
+ )} + diff --git a/packages/app/src/components/__tests__/DBSearchPageFilters.test.tsx b/packages/app/src/components/__tests__/DBSearchPageFilters.test.tsx index f1c54849b9..a275dbcec0 100644 --- a/packages/app/src/components/__tests__/DBSearchPageFilters.test.tsx +++ b/packages/app/src/components/__tests__/DBSearchPageFilters.test.tsx @@ -691,4 +691,27 @@ describe('NestedFilterGroup', () => { expect(screen.queryAllByTestId(/nested-filter-group-attr/)).toHaveLength(0); }); + + it('should allow reopening after close (panel stays accessible)', async () => { + renderWithMantine( + , + ); + + // Initially closed + const panel = screen.getByTestId('nested-filter-group-panel'); + expect(panel).toHaveAttribute('aria-hidden', 'true'); + + // Open the group + const control = screen.getByTestId('nested-filter-group-control'); + await userEvent.click(control); + expect(panel).toHaveAttribute('aria-hidden', 'false'); + + // Close the group + await userEvent.click(control); + expect(panel).toHaveAttribute('aria-hidden', 'true'); + + // Reopen the group — should not be stuck closed + await userEvent.click(control); + expect(panel).toHaveAttribute('aria-hidden', 'false'); + }); }); From de1ec845e8fe8dde3f66c42a07e68be5293a140e Mon Sep 17 00:00:00 2001 From: Brandon Pereira Date: Fri, 5 Jun 2026 08:21:32 -0600 Subject: [PATCH 2/2] add changeset --- .changeset/angry-boats-lie.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/angry-boats-lie.md diff --git a/.changeset/angry-boats-lie.md b/.changeset/angry-boats-lie.md new file mode 100644 index 0000000000..a5e9d5fc74 --- /dev/null +++ b/.changeset/angry-boats-lie.md @@ -0,0 +1,5 @@ +--- +"@hyperdx/app": patch +--- + +fix(search-filters): prevent nested filter dropdowns from disappearing on reopen