You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by jl...@apache.org on 2026/09/04 01:15:56 UTC
(superset) branch fix-dashboard-header-metadata-bar-overlap created (now cafb9b6eb32)
This is an automated email from the ASF dual-hosted git repository.
sadpandajoe pushed a change to branch fix-dashboard-header-metadata-bar-overlap
in repository https://gitbox.apache.org/repos/asf/superset.git
at cafb9b6eb32 fix(dashboard): clip header metadata bar instead of overlapping actions menu
This branch includes the following new commits:
new cafb9b6eb32 fix(dashboard): clip header metadata bar instead of overlapping actions menu
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(superset) 01/01: fix(dashboard): clip header metadata bar instead of overlapping actions menu
Posted by jl...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
sadpandajoe pushed a commit to branch fix-dashboard-header-metadata-bar-overlap
in repository https://gitbox.apache.org/repos/asf/superset.git
commit cafb9b6eb32c8b132f180ec9bfdc4dd3d70f8bdf
Author: sadpandajoe <jc...@gmail.com>
AuthorDate: Fri Sep 4 01:15:40 2026 +0000
fix(dashboard): clip header metadata bar instead of overlapping actions menu
On the dashboard page header, narrowing the browser window shrinks the
editable title down to nothing, but the certification/fave-star badges and
metadata bar (last modified / owner info) had no way to shrink or clip. Once
the title fully collapsed, that cluster rendered outside its allotted flex
space and visually overlapped the kebab-menu/action buttons instead of
collapsing.
Add overflow: hidden to the title panel's badges/metadata-bar wrapper so it
clips along with the title as space runs out.
Co-Authored-By: Claude Sonnet 5 <no...@anthropic.com>
---
superset-frontend/package.json | 1 +
.../PageHeaderWithActions.stories.tsx | 83 +++++++++++++++++
.../PageHeaderWithActions.test.tsx | 18 +++-
.../src/components/PageHeaderWithActions/index.tsx | 3 +-
.../dashboard-header-metadata-bar.spec.ts | 100 +++++++++++++++++++++
.../playwright/storybook/playwright.config.ts | 72 +++++++++++++++
6 files changed, 275 insertions(+), 2 deletions(-)
diff --git a/superset-frontend/package.json b/superset-frontend/package.json
index 57c63b55dbb..bb188563440 100644
--- a/superset-frontend/package.json
+++ b/superset-frontend/package.json
@@ -78,6 +78,7 @@
"playwright:debug": "playwright test --debug",
"playwright:report": "playwright show-report",
"docs:screenshots": "playwright test --config=playwright/generators/playwright.config.ts docs/",
+ "playwright:storybook": "playwright test --config=playwright/storybook/playwright.config.ts",
"prod": "npm run build",
"prune": "rm -rf ./{packages,plugins}/*/{node_modules,lib,esm,tsconfig.tsbuildinfo,package-lock.json} ./.temp_cache",
"storybook": "cross-env NODE_ENV=development BABEL_ENV=development storybook dev -p 6006",
diff --git a/superset-frontend/packages/superset-ui-core/src/components/PageHeaderWithActions/PageHeaderWithActions.stories.tsx b/superset-frontend/packages/superset-ui-core/src/components/PageHeaderWithActions/PageHeaderWithActions.stories.tsx
new file mode 100644
index 00000000000..2c9156b4b07
--- /dev/null
+++ b/superset-frontend/packages/superset-ui-core/src/components/PageHeaderWithActions/PageHeaderWithActions.stories.tsx
@@ -0,0 +1,83 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import MetadataBar, { MetadataType } from '../MetadataBar';
+import { Menu } from '../Menu';
+import { PageHeaderWithActions, PageHeaderWithActionsProps } from '.';
+
+export default {
+ title: 'Design System/Components/PageHeaderWithActions',
+ component: PageHeaderWithActions,
+ parameters: {
+ layout: 'fullscreen',
+ docs: {
+ description: {
+ component:
+ 'Header used on entity pages (e.g. the dashboard page) combining an editable title with badges, a metadata bar, and page-level actions.',
+ },
+ },
+ },
+};
+
+// Mirrors src/dashboard/components/Header's real composition: an editable
+// title plus a MetadataBar (Last Modified + Editor) rendered in
+// titlePanelAdditionalItems, so this story reproduces the header's real
+// narrow-viewport layout behavior, not just the isolated MetadataBar.
+export const DashboardHeader = (args: PageHeaderWithActionsProps) => (
+ <PageHeaderWithActions {...args} />
+);
+
+DashboardHeader.args = {
+ editableTitleProps: {
+ title: 'Q3 Executive Revenue and Growth Overview Dashboard',
+ placeholder: 'Add the name of the dashboard',
+ onSave: () => {},
+ canEdit: true,
+ label: 'Dashboard title',
+ },
+ showTitlePanelItems: true,
+ certificatiedBadgeProps: {},
+ showFaveStar: true,
+ faveStarProps: { itemId: 1, saveFaveStar: () => {}, isStarred: false },
+ titlePanelAdditionalItems: (
+ <MetadataBar
+ tooltipPlacement="bottom"
+ items={[
+ {
+ type: MetadataType.LastModified,
+ value: '2 hours ago',
+ modifiedBy: 'Jane Doe',
+ },
+ {
+ type: MetadataType.Editor,
+ createdBy: 'Jane Doe',
+ editors: ['Jane Doe', 'John Smith'],
+ createdOn: 'a week ago',
+ },
+ ]}
+ />
+ ),
+ rightPanelAdditionalItems: <button type="button">Edit dashboard</button>,
+ additionalActionsMenu: (
+ <Menu
+ items={[{ label: 'Edit properties', key: '1' }]}
+ data-test="additional-actions-menu"
+ />
+ ),
+ menuDropdownProps: {},
+};
diff --git a/superset-frontend/packages/superset-ui-core/src/components/PageHeaderWithActions/PageHeaderWithActions.test.tsx b/superset-frontend/packages/superset-ui-core/src/components/PageHeaderWithActions/PageHeaderWithActions.test.tsx
index 2a3d17c9a55..bbedf8264ee 100644
--- a/superset-frontend/packages/superset-ui-core/src/components/PageHeaderWithActions/PageHeaderWithActions.test.tsx
+++ b/superset-frontend/packages/superset-ui-core/src/components/PageHeaderWithActions/PageHeaderWithActions.test.tsx
@@ -18,7 +18,12 @@
*/
import { render, screen, userEvent } from '@superset-ui/core/spec';
-import { PageHeaderWithActions, PageHeaderWithActionsProps } from './index';
+import { supersetTheme } from '@apache-superset/core/theme';
+import {
+ buttonsStyles,
+ PageHeaderWithActions,
+ PageHeaderWithActionsProps,
+} from './index';
import { Menu } from '../Menu';
const defaultProps: PageHeaderWithActionsProps = {
@@ -54,3 +59,14 @@ test('Renders', async () => {
await userEvent.click(screen.getByLabelText('Menu actions trigger'));
expect(defaultProps.menuDropdownProps.onOpenChange).toHaveBeenCalled();
});
+
+test('clips the title panel buttons/metadata cluster instead of letting it overflow into the actions menu', () => {
+ // jsdom doesn't compute real flexbox layout, so it can't verify the
+ // overlap itself is fixed -- that's covered by the real-browser
+ // playwright/storybook/dashboard-header-metadata-bar.spec.ts spec. This
+ // guards the underlying CSS from regressing: without `overflow: hidden`,
+ // the badges/metadata-bar cluster can render outside its allotted flex
+ // space and overlap the actions menu once the title has fully collapsed.
+ const { styles } = buttonsStyles(supersetTheme);
+ expect(styles).toMatch(/overflow:\s*hidden/);
+});
diff --git a/superset-frontend/packages/superset-ui-core/src/components/PageHeaderWithActions/index.tsx b/superset-frontend/packages/superset-ui-core/src/components/PageHeaderWithActions/index.tsx
index cd1ef78f834..d280d2d8eb9 100644
--- a/superset-frontend/packages/superset-ui-core/src/components/PageHeaderWithActions/index.tsx
+++ b/superset-frontend/packages/superset-ui-core/src/components/PageHeaderWithActions/index.tsx
@@ -99,9 +99,10 @@ const headerStyles = (theme: SupersetTheme) => css`
}
`;
-const buttonsStyles = (theme: SupersetTheme) => css`
+export const buttonsStyles = (theme: SupersetTheme) => css`
display: flex;
align-items: center;
+ overflow: hidden;
padding-left: ${theme.sizeUnit * 2}px;
& .anticon-star {
diff --git a/superset-frontend/playwright/storybook/dashboard-header-metadata-bar.spec.ts b/superset-frontend/playwright/storybook/dashboard-header-metadata-bar.spec.ts
new file mode 100644
index 00000000000..9a8b42905ae
--- /dev/null
+++ b/superset-frontend/playwright/storybook/dashboard-header-metadata-bar.spec.ts
@@ -0,0 +1,100 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { test, expect, Page } from '@playwright/test';
+
+const STORY_URL =
+ '/iframe.html?id=design-system-components-pageheaderwithactions--dashboard-header&viewMode=story';
+
+// Sweeps from a comfortably wide header down to a narrow browser window,
+// matching the reported repro (shrinking the browser window narrows the
+// header until the metadata bar/owner info overlaps the kebab menu instead
+// of collapsing).
+const VIEWPORT_WIDTHS = [1200, 900, 700, 600, 500, 450, 400, 360, 320];
+
+type Rect = { left: number; top: number; right: number; bottom: number };
+
+// A clipped ancestor (overflow: hidden/auto/scroll/clip) hides any part of
+// an element that falls outside its box, so the element's raw
+// getBoundingClientRect() overstates what a user can actually see. Intersect
+// it with every clipping ancestor to get the rect that's actually rendered,
+// which is what "does this overlap something else on screen" needs to check.
+const getVisibleRect = (page: Page, selector: string): Promise<Rect> =>
+ page.$eval(selector, el => {
+ const rect = el.getBoundingClientRect();
+ const visible = {
+ left: rect.left,
+ top: rect.top,
+ right: rect.right,
+ bottom: rect.bottom,
+ };
+ let node = el.parentElement;
+ while (node) {
+ const { overflow, overflowX, overflowY } = getComputedStyle(node);
+ if (
+ overflow !== 'visible' ||
+ overflowX !== 'visible' ||
+ overflowY !== 'visible'
+ ) {
+ const ancestorRect = node.getBoundingClientRect();
+ visible.left = Math.max(visible.left, ancestorRect.left);
+ visible.top = Math.max(visible.top, ancestorRect.top);
+ visible.right = Math.min(visible.right, ancestorRect.right);
+ visible.bottom = Math.min(visible.bottom, ancestorRect.bottom);
+ }
+ node = node.parentElement;
+ }
+ return visible;
+ });
+
+const isEmpty = (rect: Rect) =>
+ rect.right <= rect.left || rect.bottom <= rect.top;
+
+const rectsIntersect = (a: Rect, b: Rect) =>
+ !isEmpty(a) &&
+ !isEmpty(b) &&
+ a.left < b.right &&
+ b.left < a.right &&
+ a.top < b.bottom &&
+ b.top < a.bottom;
+
+test('dashboard header metadata bar never overlaps the actions menu as the window narrows', async ({
+ page,
+}) => {
+ for (const width of VIEWPORT_WIDTHS) {
+ await page.setViewportSize({ width, height: 800 });
+ await page.goto(STORY_URL, { waitUntil: 'networkidle' });
+
+ await expect(page.locator('[data-test="metadata-bar"]')).toBeVisible();
+ await expect(page.locator('.right-button-panel')).toBeVisible();
+
+ const metadataBarRect = await getVisibleRect(
+ page,
+ '[data-test="metadata-bar"]',
+ );
+ const rightButtonPanelRect = await getVisibleRect(
+ page,
+ '.right-button-panel',
+ );
+
+ expect(
+ rectsIntersect(metadataBarRect, rightButtonPanelRect),
+ `metadata bar visually overlapped the actions menu panel at viewport width ${width}px`,
+ ).toBe(false);
+ }
+});
diff --git a/superset-frontend/playwright/storybook/playwright.config.ts b/superset-frontend/playwright/storybook/playwright.config.ts
new file mode 100644
index 00000000000..3f911e13b5d
--- /dev/null
+++ b/superset-frontend/playwright/storybook/playwright.config.ts
@@ -0,0 +1,72 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Playwright config for real-browser layout tests that render components
+ * directly from Storybook, rather than the full running application.
+ *
+ * These verify CSS/flexbox layout behavior (e.g. overlap, truncation) that
+ * jsdom cannot compute, without requiring the Flask/DB backend that the main
+ * playwright.config.ts targets. Run via:
+ * npm run playwright:storybook
+ */
+
+/// <reference types="node" />
+
+// eslint-disable-next-line import/no-extraneous-dependencies
+import { defineConfig } from '@playwright/test';
+
+const storybookURL = process.env.STORYBOOK_URL || 'http://localhost:6006';
+
+export default defineConfig({
+ testDir: '.',
+
+ timeout: 30000,
+ expect: { timeout: 8000 },
+
+ fullyParallel: true,
+ workers: process.env.CI ? 2 : undefined,
+ retries: process.env.CI ? 2 : 0,
+
+ reporter: [['list']],
+
+ use: {
+ baseURL: storybookURL,
+ headless: true,
+ screenshot: 'only-on-failure',
+ trace: 'retain-on-failure',
+ },
+
+ projects: [
+ {
+ name: 'storybook',
+ use: {
+ browserName: 'chromium',
+ testIdAttribute: 'data-test',
+ },
+ },
+ ],
+
+ webServer: {
+ command: 'npm run storybook',
+ url: storybookURL,
+ reuseExistingServer: true,
+ timeout: 120000,
+ },
+});