diff --git a/superset-frontend/packages/superset-ui-core/src/components/ActionButton/ActionButton.test.tsx b/superset-frontend/packages/superset-ui-core/src/components/ActionButton/ActionButton.test.tsx
new file mode 100644
index 000000000000..123d53495056
--- /dev/null
+++ b/superset-frontend/packages/superset-ui-core/src/components/ActionButton/ActionButton.test.tsx
@@ -0,0 +1,108 @@
+/**
+ * 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 { render, screen, userEvent } from '@superset-ui/core/spec';
+import { Icons } from '@superset-ui/core/components/Icons';
+import { ActionButton } from '.';
+
+const defaultProps = {
+ label: 'test-action',
+ icon: ,
+ onClick: jest.fn(),
+};
+
+test('renders action button with icon', () => {
+ render();
+
+ const button = screen.getByRole('button');
+ expect(button).toBeInTheDocument();
+ expect(button).toHaveAttribute('data-test', 'test-action');
+ expect(button).toHaveClass('action-button');
+});
+
+test('calls onClick when clicked', async () => {
+ const onClick = jest.fn();
+ render();
+
+ const button = screen.getByRole('button');
+ userEvent.click(button);
+
+ expect(onClick).toHaveBeenCalledTimes(1);
+});
+
+test('renders with tooltip when tooltip prop is provided', async () => {
+ const tooltipText = 'This is a tooltip';
+ render();
+
+ const button = screen.getByRole('button');
+ userEvent.hover(button);
+
+ const tooltip = await screen.findByRole('tooltip');
+ expect(tooltip).toBeInTheDocument();
+ expect(tooltip).toHaveTextContent(tooltipText);
+});
+
+test('renders without tooltip when tooltip prop is not provided', async () => {
+ render();
+
+ const button = screen.getByRole('button');
+ userEvent.hover(button);
+
+ const tooltip = screen.queryByRole('tooltip');
+ expect(tooltip).not.toBeInTheDocument();
+});
+
+test('supports ReactElement tooltip', async () => {
+ const tooltipElement =
Custom tooltip content
;
+ render();
+
+ const button = screen.getByRole('button');
+ userEvent.hover(button);
+
+ const tooltip = await screen.findByRole('tooltip');
+ expect(tooltip).toBeInTheDocument();
+ expect(tooltip).toHaveTextContent('Custom tooltip content');
+});
+
+test('renders different icons correctly', () => {
+ render(} />);
+
+ const button = screen.getByRole('button');
+ expect(button).toBeInTheDocument();
+});
+
+test('renders with custom placement for tooltip', async () => {
+ const tooltipText = 'Tooltip with custom placement';
+ render(
+ ,
+ );
+
+ const button = screen.getByRole('button');
+ userEvent.hover(button);
+
+ const tooltip = await screen.findByRole('tooltip');
+ expect(tooltip).toBeInTheDocument();
+});
+
+test('has proper accessibility attributes', () => {
+ render();
+
+ const button = screen.getByRole('button');
+ expect(button).toHaveAttribute('tabIndex', '0');
+ expect(button).toHaveAttribute('role', 'button');
+});
diff --git a/superset-frontend/packages/superset-ui-core/src/components/ActionButton/index.tsx b/superset-frontend/packages/superset-ui-core/src/components/ActionButton/index.tsx
new file mode 100644
index 000000000000..38ccdda399fc
--- /dev/null
+++ b/superset-frontend/packages/superset-ui-core/src/components/ActionButton/index.tsx
@@ -0,0 +1,75 @@
+/**
+ * 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 type { ReactElement } from 'react';
+import {
+ Tooltip,
+ type TooltipPlacement,
+ type IconType,
+} from '@superset-ui/core/components';
+import { css, useTheme } from '@superset-ui/core';
+
+export interface ActionProps {
+ label: string;
+ tooltip?: string | ReactElement;
+ placement?: TooltipPlacement;
+ icon: IconType;
+ onClick: () => void;
+}
+
+export const ActionButton = ({
+ label,
+ tooltip,
+ placement,
+ icon,
+ onClick,
+}: ActionProps) => {
+ const theme = useTheme();
+ const actionButton = (
+
+ {icon}
+
+ );
+
+ const tooltipId = `${label.replaceAll(' ', '-').toLowerCase()}-tooltip`;
+
+ return tooltip ? (
+
+ {actionButton}
+
+ ) : (
+ actionButton
+ );
+};
diff --git a/superset-frontend/packages/superset-ui-core/src/components/Tabs/Tabs.tsx b/superset-frontend/packages/superset-ui-core/src/components/Tabs/Tabs.tsx
index d834cd05f22d..edc50623f758 100644
--- a/superset-frontend/packages/superset-ui-core/src/components/Tabs/Tabs.tsx
+++ b/superset-frontend/packages/superset-ui-core/src/components/Tabs/Tabs.tsx
@@ -21,15 +21,18 @@ import { css, styled, useTheme } from '@superset-ui/core';
// eslint-disable-next-line no-restricted-imports
import { Tabs as AntdTabs, TabsProps as AntdTabsProps } from 'antd';
import { Icons } from '@superset-ui/core/components/Icons';
+import type { SerializedStyles } from '@emotion/react';
export interface TabsProps extends AntdTabsProps {
allowOverflow?: boolean;
+ contentStyle?: SerializedStyles;
}
const StyledTabs = ({
animated = false,
allowOverflow = true,
tabBarStyle,
+ contentStyle,
...props
}: TabsProps) => {
const theme = useTheme();
@@ -46,6 +49,7 @@ const StyledTabs = ({
.ant-tabs-content-holder {
overflow: ${allowOverflow ? 'visible' : 'auto'};
+ ${contentStyle}
}
.ant-tabs-tab {
flex: 1 1 auto;
@@ -85,9 +89,10 @@ const Tabs = Object.assign(StyledTabs, {
});
const StyledEditableTabs = styled(StyledTabs)`
- ${({ theme }) => `
+ ${({ theme, contentStyle }) => `
.ant-tabs-content-holder {
background: ${theme.colorBgContainer};
+ ${contentStyle}
}
& > .ant-tabs-nav {
diff --git a/superset-frontend/packages/superset-ui-core/src/components/index.ts b/superset-frontend/packages/superset-ui-core/src/components/index.ts
index 6e0cf8f6adcd..55829c0b1c6e 100644
--- a/superset-frontend/packages/superset-ui-core/src/components/index.ts
+++ b/superset-frontend/packages/superset-ui-core/src/components/index.ts
@@ -189,3 +189,4 @@ export {
type CodeEditorMode,
type CodeEditorTheme,
} from './CodeEditor';
+export { ActionButton, type ActionProps } from './ActionButton';
diff --git a/superset-frontend/src/SqlLab/components/QueryHistory/index.tsx b/superset-frontend/src/SqlLab/components/QueryHistory/index.tsx
index 0a311d48084b..0ae67caa3657 100644
--- a/superset-frontend/src/SqlLab/components/QueryHistory/index.tsx
+++ b/superset-frontend/src/SqlLab/components/QueryHistory/index.tsx
@@ -27,6 +27,7 @@ import {
css,
FeatureFlag,
isFeatureEnabled,
+ useTheme,
} from '@superset-ui/core';
import QueryTable from 'src/SqlLab/components/QueryTable';
import { SqlLabRootState } from 'src/SqlLab/types';
@@ -67,6 +68,7 @@ const QueryHistory = ({
const { id, tabViewId } = useQueryEditor(String(queryEditorId), [
'tabViewId',
]);
+ const theme = useTheme();
const editorId = tabViewId ?? id;
const [ref, hasReachedBottom] = useInView({ threshold: 0 });
const [pageIndex, setPageIndex] = useState(0);
@@ -118,7 +120,11 @@ const QueryHistory = ({
}
return editorQueries.length > 0 ? (
- <>
+