Compare commits

...

4 Commits

Author SHA1 Message Date
Evan Rusackas
bf6e779200 address review: use Button tooltip prop so hint shows on disabled trigger
AntD disabled buttons swallow hover/focus, so the previous
<Tooltip><Button disabled /></Tooltip> wrapper left the
"No applied filters" hint unreachable in the exact state this PR
introduces (button visible, no popover content). The superset-ui Button
component's built-in `tooltip` prop already wraps disabled buttons in a
<span> so the tooltip fires — switch to that.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 16:01:03 -07:00
Evan Rusackas
ce6501c36c address review: guard empty popover and disable trigger when no content 2026-04-22 15:58:53 -07:00
Evan Rusackas
6ea6b01f4f address review: timeless comment wording 2026-04-22 15:58:24 -07:00
Evan Rusackas
c1376c05a9 fix(FilterBar): always show 'More filters' button when items exist
This fixes issue #28060 where the "More filters" button would disappear
when filter values were set to their defaults and nothing was overflowing.

The issue was caused by the button only rendering when `popoverContent`
was truthy, which required either `dropdownContent` to be defined OR
`overflowingCount > 0`. When neither condition was met (common when all
filters fit in the container), the button would vanish, causing:
- Inconsistent UI behavior
- Layout shifts as the button appears/disappears
- Poor user experience when resizing the browser window

The fix introduces a `shouldShowButton` flag that ensures the button is
always visible when items exist, regardless of overflow state. The badge
correctly shows 0 when nothing is overflowing, providing clear feedback
to users.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-22 15:58:24 -07:00
2 changed files with 74 additions and 54 deletions

View File

@@ -51,8 +51,16 @@ test('renders children with custom horizontal spacing', () => {
expect(screen.getByTestId('container')).toHaveStyle('gap: 20px');
});
test('does not render a dropdown button when not overflowing', () => {
test('renders dropdown button when items exist even when not overflowing', () => {
render(<DropdownContainer items={generateItems(3)} />);
// Button should always be visible when items exist to prevent layout shifts
expect(screen.getByText('More')).toBeInTheDocument();
// Badge should show 0 when nothing is overflowing
expect(screen.getByText('0')).toBeInTheDocument();
});
test('does not render a dropdown button when no items', () => {
render(<DropdownContainer items={[]} />);
expect(screen.queryByText('More')).not.toBeInTheDocument();
});

View File

@@ -34,7 +34,7 @@ import { t } from '@apache-superset/core/translation';
import { usePrevious } from '@superset-ui/core';
import { css, useTheme } from '@apache-superset/core/theme';
import { useResizeDetector } from 'react-resize-detector';
import { Badge, Icons, Button, Tooltip, Popover } from '..';
import { Badge, Icons, Button, Popover } from '..';
import { DropdownContainerProps, DropdownItem, DropdownRef } from './types';
const MAX_HEIGHT = 500;
@@ -234,6 +234,10 @@ export const DropdownContainer = forwardRef(
const overflowingCount =
overflowingIndex !== -1 ? items.length - overflowingIndex : 0;
// Always show button when items exist to prevent layout shifts
// and ensure consistent UI even when no items are overflowing
const shouldShowButton = items.length > 0 || !!dropdownContent;
const popoverContent = useMemo(
() =>
dropdownContent || overflowingCount ? (
@@ -293,6 +297,44 @@ export const DropdownContainer = forwardRef(
};
}, [popoverVisible]);
const triggerButton = (
<Button
buttonStyle="secondary"
data-test="dropdown-container-btn"
icon={dropdownTriggerIcon}
disabled={!popoverContent}
tooltip={dropdownTriggerTooltip}
css={css`
padding-left: ${theme.paddingXS}px;
padding-right: ${theme.paddingXXS}px;
gap: ${theme.sizeXXS}px;
`}
>
{dropdownTriggerText}
<Badge
count={dropdownTriggerCount ?? overflowingCount}
color={
(dropdownTriggerCount ?? overflowingCount) > 0
? theme.colorPrimary
: theme.colorTextSecondary
}
showZero
css={css`
margin-left: ${theme.sizeUnit * 2}px;
`}
/>
<Icons.DownOutlined
iconSize="m"
iconColor={theme.colorIcon}
css={css`
.anticon {
display: flex;
}
`}
/>
</Button>
);
return (
<div
ref={ref}
@@ -314,7 +356,7 @@ export const DropdownContainer = forwardRef(
>
{notOverflowedItems.map(item => item.element)}
</div>
{popoverContent && (
{shouldShowButton && (
<>
<Global
styles={css`
@@ -339,57 +381,27 @@ export const DropdownContainer = forwardRef(
`}
/>
<Popover
styles={{
body: {
maxHeight: `${MAX_HEIGHT}px`,
overflow: showOverflow ? 'auto' : 'visible',
},
}}
content={popoverContent}
trigger="click"
open={popoverVisible}
onOpenChange={visible => setPopoverVisible(visible)}
placement="bottom"
forceRender={forceRender}
fresh // This prop prevents caching and stale data for filter scoping.
>
<Tooltip title={dropdownTriggerTooltip}>
<Button
buttonStyle="secondary"
data-test="dropdown-container-btn"
icon={dropdownTriggerIcon}
css={css`
padding-left: ${theme.paddingXS}px;
padding-right: ${theme.paddingXXS}px;
gap: ${theme.sizeXXS}px;
`}
>
{dropdownTriggerText}
<Badge
count={dropdownTriggerCount ?? overflowingCount}
color={
(dropdownTriggerCount ?? overflowingCount) > 0
? theme.colorPrimary
: theme.colorTextSecondary
}
showZero
css={css`
margin-left: ${theme.sizeUnit * 2}px;
`}
/>
<Icons.DownOutlined
iconSize="m"
iconColor={theme.colorIcon}
css={css`
.anticon {
display: flex;
}
`}
/>
</Button>
</Tooltip>
</Popover>
{popoverContent ? (
<Popover
styles={{
body: {
maxHeight: `${MAX_HEIGHT}px`,
overflow: showOverflow ? 'auto' : 'visible',
},
}}
content={popoverContent}
trigger="click"
open={popoverVisible}
onOpenChange={visible => setPopoverVisible(visible)}
placement="bottom"
forceRender={forceRender}
fresh // This prop prevents caching and stale data for filter scoping.
>
{triggerButton}
</Popover>
) : (
triggerButton
)}
</>
)}
</div>