This is an automated email from the ASF dual-hosted git repository.
msyavuz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new 967134f540 fix(theming): Visual bugs p-3 (#34424)
967134f540 is described below
commit 967134f540b279df00b3856d7d688a14b490985f
Author: Mehmet Salih Yavuz <[email protected]>
AuthorDate: Fri Aug 1 00:26:38 2025 +0300
fix(theming): Visual bugs p-3 (#34424)
---
.../cypress/e2e/dashboard/drillby.test.ts | 17 +++---
.../Chart/ChartContextMenu/ChartContextMenu.tsx | 10 +++-
.../Chart/DrillBy/DrillByMenuItems.test.tsx | 43 +++++++++++----
.../components/Chart/DrillBy/DrillByMenuItems.tsx | 10 ++--
.../components/Chart/MenuItemWithTruncation.tsx | 63 ++++++++++++++++++++--
.../explore/components/ControlPanelsContainer.tsx | 19 +++----
.../DateFilterControl/components/DateLabel.tsx | 14 ++---
7 files changed, 133 insertions(+), 43 deletions(-)
diff --git
a/superset-frontend/cypress-base/cypress/e2e/dashboard/drillby.test.ts
b/superset-frontend/cypress-base/cypress/e2e/dashboard/drillby.test.ts
index d0b10f05ee..b8ded5ee1b 100644
--- a/superset-frontend/cypress-base/cypress/e2e/dashboard/drillby.test.ts
+++ b/superset-frontend/cypress-base/cypress/e2e/dashboard/drillby.test.ts
@@ -65,11 +65,16 @@ const drillBy = (targetDrillByColumn: string, isLegacy =
false) => {
)
.should('be.visible')
.find('[role="menuitem"]')
- .then($el => {
- cy.wrap($el)
- .contains(new RegExp(`^${targetDrillByColumn}$`))
- .trigger('keydown', { keyCode: 13, which: 13, force: true });
- });
+ .contains(new RegExp(`^${targetDrillByColumn}$`))
+ .click();
+
+ cy.get(
+ '.ant-dropdown-menu-submenu:not(.ant-dropdown-menu-submenu-hidden)
[data-test="drill-by-submenu"]',
+ ).trigger('mouseout', { clientX: 0, clientY: 0, force: true });
+
+ cy.get(
+ '.ant-dropdown-menu-submenu:not(.ant-dropdown-menu-submenu-hidden)
[data-test="drill-by-submenu"]',
+ ).should('not.exist');
if (isLegacy) {
return cy.wait('@legacyData');
@@ -240,7 +245,7 @@ describe('Drill by modal', () => {
SUPPORTED_TIER1_CHARTS.forEach(waitForChartLoad);
});
- it('opens the modal from the context menu', () => {
+ it.only('opens the modal from the context menu', () => {
openTableContextMenu('boy');
drillBy('state').then(intercepted => {
verifyExpectedFormData(intercepted, {
diff --git
a/superset-frontend/src/components/Chart/ChartContextMenu/ChartContextMenu.tsx
b/superset-frontend/src/components/Chart/ChartContextMenu/ChartContextMenu.tsx
index bda9c13a83..4e3369429c 100644
---
a/superset-frontend/src/components/Chart/ChartContextMenu/ChartContextMenu.tsx
+++
b/superset-frontend/src/components/Chart/ChartContextMenu/ChartContextMenu.tsx
@@ -63,7 +63,7 @@ export enum ContextMenuItem {
export interface ChartContextMenuProps {
id: number;
formData: QueryFormData;
- onSelection: () => void;
+ onSelection: (args?: any) => void;
onClose: () => void;
additionalConfig?: {
crossFilter?: Record<string, any>;
@@ -123,6 +123,12 @@ const ChartContextMenu = (
const [dataset, setDataset] = useState<Dataset>();
const verboseMap = useVerboseMap(dataset);
+ const closeContextMenu = useCallback(() => {
+ setVisible(false);
+ setOpenKeys([]);
+ onClose();
+ }, [onClose]);
+
const handleDrillBy = useCallback((column: Column, dataset: Dataset) => {
setDrillByColumn(column);
setDataset(dataset); // Save dataset when drilling
@@ -264,6 +270,7 @@ const ChartContextMenu = (
<DrillByMenuItems
drillByConfig={filters?.drillBy}
onSelection={onSelection}
+ onCloseMenu={closeContextMenu}
formData={formData}
contextMenuY={clientY}
submenuIndex={submenuIndex}
@@ -311,6 +318,7 @@ const ChartContextMenu = (
onOpenChange={setOpenKeys}
onClick={() => {
setVisible(false);
+ setOpenKeys([]);
onClose();
}}
>
diff --git
a/superset-frontend/src/components/Chart/DrillBy/DrillByMenuItems.test.tsx
b/superset-frontend/src/components/Chart/DrillBy/DrillByMenuItems.test.tsx
index fd838f0108..9d2a9360a1 100644
--- a/superset-frontend/src/components/Chart/DrillBy/DrillByMenuItems.test.tsx
+++ b/superset-frontend/src/components/Chart/DrillBy/DrillByMenuItems.test.tsx
@@ -166,8 +166,12 @@ test('render menu item with submenu without searchbox',
async () => {
renderMenu({});
await waitFor(() => fetchMock.called(DATASET_ENDPOINT));
await expectDrillByEnabled();
+
+ // Check that each column appears in the drill-by submenu
slicedColumns.forEach(column => {
- expect(screen.getByText(column.column_name)).toBeInTheDocument();
+ const submenus = screen.getAllByTestId('drill-by-submenu');
+ const submenu = submenus[0]; // Use the first submenu
+ expect(within(submenu).getByText(column.column_name)).toBeInTheDocument();
});
expect(screen.queryByRole('textbox')).not.toBeInTheDocument();
});
@@ -186,15 +190,19 @@ test('render menu item with submenu and searchbox', async
() => {
// Wait for all columns to be visible
await waitFor(
() => {
+ const submenus = screen.getAllByTestId('drill-by-submenu');
+ const submenu = submenus[0];
defaultColumns.forEach(column => {
- expect(screen.getByText(column.column_name)).toBeInTheDocument();
+ expect(
+ within(submenu).getByText(column.column_name),
+ ).toBeInTheDocument();
});
},
{ timeout: 10000 },
);
const searchbox = await waitFor(
- () => screen.getAllByPlaceholderText('Search columns')[1],
+ () => screen.getAllByPlaceholderText('Search columns')[0],
);
expect(searchbox).toBeInTheDocument();
@@ -204,19 +212,26 @@ test('render menu item with submenu and searchbox', async
() => {
// Wait for filtered results
await waitFor(() => {
+ const submenus = screen.getAllByTestId('drill-by-submenu');
+ const submenu = submenus[0];
expectedFilteredColumnNames.forEach(colName => {
- expect(screen.getByText(colName)).toBeInTheDocument();
+ expect(within(submenu).getByText(colName)).toBeInTheDocument();
});
});
+ const submenus = screen.getAllByTestId('drill-by-submenu');
+ const submenu = submenus[0];
+
defaultColumns
.filter(col => !expectedFilteredColumnNames.includes(col.column_name))
.forEach(col => {
- expect(screen.queryByText(col.column_name)).not.toBeInTheDocument();
+ expect(
+ within(submenu).queryByText(col.column_name),
+ ).not.toBeInTheDocument();
});
expectedFilteredColumnNames.forEach(colName => {
- expect(screen.getByText(colName)).toBeInTheDocument();
+ expect(within(submenu).getByText(colName)).toBeInTheDocument();
});
});
@@ -238,17 +253,23 @@ test('Do not display excluded column in the menu', async
() => {
// Wait for menu items to be loaded
await waitFor(
() => {
+ const submenus = screen.getAllByTestId('drill-by-submenu');
+ const submenu = submenus[0];
defaultColumns
.filter(column => !excludedColNames.includes(column.column_name))
.forEach(column => {
- expect(screen.getByText(column.column_name)).toBeInTheDocument();
+ expect(
+ within(submenu).getByText(column.column_name),
+ ).toBeInTheDocument();
});
},
{ timeout: 10000 },
);
+ const submenus = screen.getAllByTestId('drill-by-submenu');
+ const submenu = submenus[0];
excludedColNames.forEach(colName => {
- expect(screen.queryByText(colName)).not.toBeInTheDocument();
+ expect(within(submenu).queryByText(colName)).not.toBeInTheDocument();
});
});
@@ -269,7 +290,11 @@ test('When menu item is clicked, call onSelection with
clicked column and drill
await expectDrillByEnabled();
// Wait for col1 to be visible before clicking
- const col1Element = await waitFor(() => screen.getByText('col1'));
+ const col1Element = await waitFor(() => {
+ const submenus = screen.getAllByTestId('drill-by-submenu');
+ const submenu = submenus[0];
+ return within(submenu).getByText('col1');
+ });
userEvent.click(col1Element);
expect(onSelectionMock).toHaveBeenCalledWith(
diff --git
a/superset-frontend/src/components/Chart/DrillBy/DrillByMenuItems.tsx
b/superset-frontend/src/components/Chart/DrillBy/DrillByMenuItems.tsx
index c0ffc795e2..ec0d4e1c21 100644
--- a/superset-frontend/src/components/Chart/DrillBy/DrillByMenuItems.tsx
+++ b/superset-frontend/src/components/Chart/DrillBy/DrillByMenuItems.tsx
@@ -54,7 +54,7 @@ import {
import { InputRef } from 'antd';
import { MenuItemTooltip } from '../DisabledMenuItemTooltip';
import { getSubmenuYOffset } from '../utils';
-import { MenuItemWithTruncation } from '../MenuItemWithTruncation';
+import { VirtualizedMenuItem } from '../MenuItemWithTruncation';
import { Dataset } from '../types';
const SUBMENU_HEIGHT = 200;
@@ -68,6 +68,7 @@ export interface DrillByMenuItemsProps {
submenuIndex?: number;
onSelection?: (...args: any) => void;
onClick?: (event: MouseEvent) => void;
+ onCloseMenu?: () => void;
openNewModal?: boolean;
excludedColumns?: Column[];
open: boolean;
@@ -100,6 +101,7 @@ export const DrillByMenuItems = ({
submenuIndex = 0,
onSelection = () => {},
onClick = () => {},
+ onCloseMenu = () => {},
excludedColumns,
openNewModal = true,
open,
@@ -124,6 +126,7 @@ export const DrillByMenuItems = ({
if (openNewModal && onDrillBy && dataset) {
onDrillBy(column, dataset);
}
+ onCloseMenu();
},
[drillByConfig, onClick, onSelection, openNewModal, onDrillBy, dataset],
);
@@ -264,15 +267,14 @@ export const DrillByMenuItems = ({
const { columns, ...rest } = data;
const column = columns[index];
return (
- <MenuItemWithTruncation
- menuKey={`drill-by-item-${column.column_name}`}
+ <VirtualizedMenuItem
tooltipText={column.verbose_name || column.column_name}
onClick={e => handleSelection(e, column)}
style={style}
{...rest}
>
{column.verbose_name || column.column_name}
- </MenuItemWithTruncation>
+ </VirtualizedMenuItem>
);
};
diff --git a/superset-frontend/src/components/Chart/MenuItemWithTruncation.tsx
b/superset-frontend/src/components/Chart/MenuItemWithTruncation.tsx
index 00311c38ad..d766ee780b 100644
--- a/superset-frontend/src/components/Chart/MenuItemWithTruncation.tsx
+++ b/superset-frontend/src/components/Chart/MenuItemWithTruncation.tsx
@@ -18,9 +18,14 @@
*/
import { ReactNode, CSSProperties, useCallback } from 'react';
-import { css, truncationCSS, useCSSTextTruncation } from '@superset-ui/core';
+import {
+ css,
+ truncationCSS,
+ useCSSTextTruncation,
+ useTheme,
+} from '@superset-ui/core';
import { Menu, type ItemType } from '@superset-ui/core/components/Menu';
-import { Tooltip } from '@superset-ui/core/components';
+import { Flex, Tooltip } from '@superset-ui/core/components';
import { MenuItemProps } from 'antd';
export type MenuItemWithTruncationProps = {
@@ -113,7 +118,12 @@ export const MenuItemWithTruncation = ({
onClick={onClick}
style={style}
>
- <Tooltip title={itemIsTruncated ? tooltipText : null}>
+ <Tooltip
+ title={itemIsTruncated ? tooltipText : null}
+ css={css`
+ max-width: 200px;
+ `}
+ >
<div
ref={itemRef}
css={css`
@@ -127,3 +137,50 @@ export const MenuItemWithTruncation = ({
</Menu.Item>
);
};
+
+export const VirtualizedMenuItem = ({
+ tooltipText,
+ children,
+ onClick,
+ style,
+}: {
+ tooltipText: ReactNode;
+ children: ReactNode;
+ onClick?: (e: React.MouseEvent) => void;
+ style?: CSSProperties;
+}) => {
+ const theme = useTheme();
+ const [itemRef, itemIsTruncated] = useCSSTextTruncation<HTMLDivElement>();
+
+ return (
+ <Flex
+ role="menuitem"
+ tabIndex={0}
+ onClick={onClick}
+ align="center"
+ style={style}
+ css={css`
+ cursor: pointer;
+ padding-left: ${theme.paddingXS}px;
+ &:hover {
+ background-color: ${theme.colorBgTextHover};
+ }
+ &:active {
+ background-color: ${theme.colorBgTextActive};
+ }
+ `}
+ >
+ <Tooltip title={itemIsTruncated ? tooltipText : null}>
+ <div
+ ref={itemRef}
+ css={css`
+ max-width: 100%;
+ ${truncationCSS};
+ `}
+ >
+ {children}
+ </div>
+ </Tooltip>
+ </Flex>
+ );
+};
diff --git
a/superset-frontend/src/explore/components/ControlPanelsContainer.tsx
b/superset-frontend/src/explore/components/ControlPanelsContainer.tsx
index 09c6b0f3b4..83524d102a 100644
--- a/superset-frontend/src/explore/components/ControlPanelsContainer.tsx
+++ b/superset-frontend/src/explore/components/ControlPanelsContainer.tsx
@@ -53,7 +53,6 @@ import {
sections,
} from '@superset-ui/chart-controls';
import { useSelector } from 'react-redux';
-import { rgba } from 'emotion-rgba';
import { kebabCase, isEqual } from 'lodash';
import {
@@ -118,16 +117,11 @@ const iconStyles = css`
const actionButtonsContainerStyles = (theme: SupersetTheme) => css`
display: flex;
- position: sticky;
- bottom: 0;
flex-direction: column;
align-items: center;
padding: ${theme.sizeUnit * 4}px;
- z-index: 999;
- background: linear-gradient(
- ${rgba(theme.colorBgBase, 0)},
- ${theme.colorBgBase} 35%
- );
+ background: ${theme.colorBgContainer};
+ flex-shrink: 0;
& > button {
min-width: 156px;
@@ -138,15 +132,18 @@ const Styles = styled.div`
position: relative;
height: 100%;
width: 100%;
+ display: flex;
+ flex-direction: column;
// Resizable add overflow-y: auto as a style to this div
// To override it, we need to use !important
overflow: visible !important;
+
#controlSections {
- height: 100%;
- overflow: visible;
- padding-bottom: ${({ theme }) => theme.sizeUnit * 10}px;
+ flex: 1;
+ overflow: auto;
}
+
.tab-content {
overflow: auto;
flex: 1 1 100%;
diff --git
a/superset-frontend/src/explore/components/controls/DateFilterControl/components/DateLabel.tsx
b/superset-frontend/src/explore/components/controls/DateFilterControl/components/DateLabel.tsx
index b4643bf32b..67d076e60e 100644
---
a/superset-frontend/src/explore/components/controls/DateFilterControl/components/DateLabel.tsx
+++
b/superset-frontend/src/explore/components/controls/DateFilterControl/components/DateLabel.tsx
@@ -30,10 +30,6 @@ export type DateLabelProps = {
onClick?: (event: MouseEvent) => void;
};
-// This is the color that antd components (such as Select or Input) use on
hover
-// TODO: use theme.colorPrimary here and in antd components
-const ACTIVE_BORDER_COLOR = '#45BED6';
-
const LabelContainer = styled.div<{
isActive?: boolean;
isPlaceholder?: boolean;
@@ -47,10 +43,9 @@ const LabelContainer = styled.div<{
padding: 0 ${theme.sizeUnit * 3}px;
- background-color: ${theme.colors.grayscale.light5};
+ background-color: ${theme.colorBgContainer};
- border: 1px solid
- ${isActive ? ACTIVE_BORDER_COLOR : theme.colors.grayscale.light2};
+ border: 1px solid ${isActive ? theme.colorPrimary : theme.colorBorder};
border-radius: ${theme.borderRadius}px;
cursor: pointer;
@@ -58,11 +53,11 @@ const LabelContainer = styled.div<{
transition: border-color 0.3s cubic-bezier(0.65, 0.05, 0.36, 1);
:hover,
:focus {
- border-color: ${ACTIVE_BORDER_COLOR};
+ border-color: ${theme.colorPrimary};
}
.date-label-content {
- color: ${isPlaceholder ? theme.colors.grayscale.light1 :
theme.colorText};
+ color: ${isPlaceholder ? theme.colorTextPlaceholder : theme.colorText};
overflow: hidden;
text-overflow: ellipsis;
min-width: 0;
@@ -71,6 +66,7 @@ const LabelContainer = styled.div<{
}
span[role='img'] {
+ color: ${isPlaceholder ? theme.colorTextPlaceholder : theme.colorText};
margin-left: auto;
padding-left: ${theme.sizeUnit}px;