This is an automated email from the ASF dual-hosted git repository.

msyavuz pushed a commit to branch msyavuz/fix/south-pane-visuals
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 50e046e2f5452191851d0fa37381f1d8b1f6c941
Author: Mehmet Salih Yavuz <[email protected]>
AuthorDate: Fri Oct 10 10:19:13 2025 +0300

    fix(SqlLab): South pane visuals
---
 .../superset-ui-core/src/components/Tabs/Tabs.tsx  |   7 +-
 .../src/SqlLab/components/SqlEditor/index.tsx      |   1 +
 .../src/SqlLab/components/TablePreview/index.tsx   | 106 ++++++++++-----------
 .../src/components/ActionButton/index.tsx          |  75 +++++++++++++++
 .../src/components/ListView/ActionsBar.tsx         |  49 +---------
 5 files changed, 135 insertions(+), 103 deletions(-)

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 d834cd05f2..edc50623f7 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/src/SqlLab/components/SqlEditor/index.tsx 
b/superset-frontend/src/SqlLab/components/SqlEditor/index.tsx
index 547f2850e4..4585ed09cd 100644
--- a/superset-frontend/src/SqlLab/components/SqlEditor/index.tsx
+++ b/superset-frontend/src/SqlLab/components/SqlEditor/index.tsx
@@ -185,6 +185,7 @@ const StyledSqlEditor = styled.div`
 
     .queryPane {
       padding: ${theme.sizeUnit * 2}px;
+      padding-left: 0px;
       + .ant-splitter-bar .ant-splitter-bar-dragger {
         &::before {
           background: transparent;
diff --git a/superset-frontend/src/SqlLab/components/TablePreview/index.tsx 
b/superset-frontend/src/SqlLab/components/TablePreview/index.tsx
index 6973adc19f..d7b0c364c8 100644
--- a/superset-frontend/src/SqlLab/components/TablePreview/index.tsx
+++ b/superset-frontend/src/SqlLab/components/TablePreview/index.tsx
@@ -25,15 +25,15 @@ import {
   getExtensionsRegistry,
   styled,
   t,
+  useTheme,
 } from '@superset-ui/core';
 import {
   SafeMarkdown,
   Alert,
   Breadcrumb,
-  Button,
   Card,
-  Dropdown,
   Skeleton,
+  Flex,
 } from '@superset-ui/core/components';
 import AutoSizer from 'react-virtualized-auto-sizer';
 import { Icons } from '@superset-ui/core/components/Icons';
@@ -47,7 +47,7 @@ import {
   useTableMetadataQuery,
 } from 'src/hooks/apiResources';
 import { runTablePreviewQuery } from 'src/SqlLab/actions/sqlLab';
-import { Menu } from '@superset-ui/core/components/Menu';
+import { ActionButton } from 'src/components/ActionButton';
 import ResultSet from '../ResultSet';
 import ShowSQL from '../ShowSQL';
 
@@ -68,23 +68,6 @@ const TABS_KEYS = {
   INDEXES: 'indexes',
   SAMPLE: 'sample',
 };
-const MENUS = [
-  {
-    key: 'refresh-table',
-    label: t('Refresh table schema'),
-    icon: <Icons.SyncOutlined iconSize="s" aria-hidden />,
-  },
-  {
-    key: 'copy-select-statement',
-    label: t('Copy SELECT statement'),
-    icon: <Icons.CopyOutlined iconSize="s" aria-hidden />,
-  },
-  {
-    key: 'show-create-view-statement',
-    label: t('Show CREATE VIEW statement'),
-    icon: <Icons.EyeOutlined iconSize="s" aria-hidden />,
-  },
-];
 const TAB_HEADER_HEIGHT = 80;
 const PREVIEW_QUERY_LIMIT = 100;
 
@@ -96,6 +79,8 @@ const Title = styled.div`
     column-gap: ${theme.sizeUnit}px;
     font-size: ${theme.fontSizeLG}px;
     font-weight: ${theme.fontWeightStrong};
+    padding-top: ${theme.sizeUnit * 2}px;
+    padding-left: ${theme.sizeUnit * 4}px;
   `}
 `;
 const renderWell = (partitions: TableMetaData['partitions']) => {
@@ -133,6 +118,7 @@ const renderWell = (partitions: 
TableMetaData['partitions']) => {
 
 const TablePreview: FC<Props> = ({ dbId, catalog, schema, tableName }) => {
   const dispatch = useDispatch();
+  const theme = useTheme();
   const [databaseName, backend, disableDataPreview] = useSelector<
     SqlLabRootState,
     string[]
@@ -240,16 +226,37 @@ const TablePreview: FC<Props> = ({ dbId, catalog, schema, 
tableName }) => {
     ],
   );
 
-  const dropdownMenu = useMemo(() => {
-    let menus = [...MENUS];
-    if (!tableData.selectStar) {
-      menus = menus.filter(({ key }) => key !== 'copy-select-statement');
-    }
-    if (!tableData.view) {
-      menus = menus.filter(({ key }) => key !== 'show-create-view-statement');
-    }
-    return menus;
-  }, [tableData.view, tableData.selectStar]);
+  const titleActions = () => (
+    <Flex
+      align="center"
+      css={css`
+        padding-left: ${theme.sizeUnit * 4}px;
+      `}
+    >
+      <ActionButton
+        label="Refresh table schema"
+        tooltip={t('Refresh table schema')}
+        icon="SyncOutlined"
+        onClick={refreshTableMetadata}
+      />
+      {tableData.selectStar && (
+        <ActionButton
+          label="Copy SELECT statement"
+          icon="CopyOutlined"
+          tooltip={t('Copy SELECT statement')}
+          onClick={() => copyStatementActionRef.current?.click()}
+        />
+      )}
+      {tableData.view && (
+        <ActionButton
+          label="Show CREATE VIEW statement"
+          icon="EyeOutlined"
+          tooltip={t('Show CREATE VIEW statement')}
+          onClick={() => showViewStatementActionRef.current?.click()}
+        />
+      )}
+    </Flex>
+  );
 
   if (isMetadataLoading) {
     return <Skeleton active />;
@@ -282,7 +289,12 @@ const TablePreview: FC<Props> = ({ dbId, catalog, schema, 
tableName }) => {
         flex-direction: column;
       `}
     >
-      <Breadcrumb separator=">">
+      <Breadcrumb
+        separator=">"
+        css={css`
+          padding-left: ${theme.sizeUnit * 4}px;
+        `}
+      >
         <Breadcrumb.Item>{backend}</Breadcrumb.Item>
         <Breadcrumb.Item>{databaseName}</Breadcrumb.Item>
         {catalog && <Breadcrumb.Item>{catalog}</Breadcrumb.Item>}
@@ -315,33 +327,7 @@ const TablePreview: FC<Props> = ({ dbId, catalog, schema, 
tableName }) => {
       <Title>
         <Icons.InsertRowAboveOutlined iconSize="l" />
         {tableName}
-        <Dropdown
-          popupRender={() => (
-            <Menu
-              onClick={({ key }) => {
-                if (key === 'refresh-table') {
-                  refreshTableMetadata();
-                }
-                if (key === 'copy-select-statement') {
-                  copyStatementActionRef.current?.click();
-                }
-                if (key === 'show-create-view-statement') {
-                  showViewStatementActionRef.current?.click();
-                }
-              }}
-              items={dropdownMenu}
-            />
-          )}
-          trigger={['click']}
-        >
-          <Button buttonSize="xsmall" buttonStyle="link">
-            <Icons.DownSquareOutlined
-              iconSize="m"
-              style={{ marginTop: 2, marginLeft: 4 }}
-              aria-label={t('Table actions')}
-            />
-          </Button>
-        </Dropdown>
+        {titleActions()}
       </Title>
       {isMetadataRefreshing ? (
         <Skeleton active />
@@ -440,7 +426,11 @@ const TablePreview: FC<Props> = ({ dbId, catalog, schema, 
tableName }) => {
                     css={css`
                       height: ${height}px;
                     `}
+                    tabBarStyle={{ paddingLeft: theme.sizeUnit * 4 }}
                     items={tabItems}
+                    contentStyle={css`
+                      padding-left: ${theme.sizeUnit * 4}px;
+                    `}
                   />
                 );
               }}
diff --git a/superset-frontend/src/components/ActionButton/index.tsx 
b/superset-frontend/src/components/ActionButton/index.tsx
new file mode 100644
index 0000000000..64685add0c
--- /dev/null
+++ b/superset-frontend/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 {
+  Icons,
+  type IconNameType,
+  Tooltip,
+  type TooltipPlacement,
+} from '@superset-ui/core/components';
+import { css, useTheme } from '@superset-ui/core';
+
+export interface ActionProps {
+  label: string;
+  tooltip?: string | ReactElement;
+  placement?: TooltipPlacement;
+  icon: string;
+  onClick: () => void;
+}
+
+export const ActionButton = ({
+  label,
+  tooltip,
+  placement,
+  icon,
+  onClick,
+}: ActionProps) => {
+  const theme = useTheme();
+  const ActionIcon = Icons[icon as IconNameType];
+  const actionButton = (
+    <span
+      role="button"
+      tabIndex={0}
+      css={css`
+        cursor: pointer;
+        color: ${theme.colorIcon};
+        margin-right: ${theme.sizeUnit}px;
+        &:hover {
+          path {
+            fill: ${theme.colorPrimary};
+          }
+        }
+      `}
+      className="action-button"
+      data-test={label}
+      onClick={onClick}
+    >
+      <ActionIcon iconSize="l" />
+    </span>
+  );
+
+  return tooltip ? (
+    <Tooltip id={`${label}-tooltip`} title={tooltip} placement={placement}>
+      {actionButton}
+    </Tooltip>
+  ) : (
+    actionButton
+  );
+};
diff --git a/superset-frontend/src/components/ListView/ActionsBar.tsx 
b/superset-frontend/src/components/ListView/ActionsBar.tsx
index 7a4f57b7fc..06b76d2e31 100644
--- a/superset-frontend/src/components/ListView/ActionsBar.tsx
+++ b/superset-frontend/src/components/ListView/ActionsBar.tsx
@@ -37,12 +37,8 @@
  */
 import { ReactElement } from 'react';
 import { styled } from '@superset-ui/core';
-import {
-  Icons,
-  IconNameType,
-  Tooltip,
-  type TooltipPlacement,
-} from '@superset-ui/core/components';
+import { type TooltipPlacement } from '@superset-ui/core/components';
+import { ActionButton } from '../ActionButton';
 
 export type ActionProps = {
   label: string;
@@ -59,49 +55,14 @@ interface ActionsBarProps {
 const StyledActions = styled.span`
   white-space: nowrap;
   min-width: 100px;
-  .action-button {
-    cursor: pointer;
-    color: ${({ theme }) => theme.colorIcon};
-    margin-right: ${({ theme }) => theme.sizeUnit}px;
-    &:hover {
-      path {
-        fill: ${({ theme }) => theme.colorPrimary};
-      }
-    }
-  }
 `;
 
 export function ActionsBar({ actions }: ActionsBarProps) {
   return (
     <StyledActions className="actions">
-      {actions.map((action, index) => {
-        const ActionIcon = Icons[action.icon as IconNameType];
-        const actionButton = (
-          <span
-            role="button"
-            tabIndex={0}
-            style={{ cursor: 'pointer' }}
-            className="action-button"
-            data-test={action.label}
-            onClick={action.onClick}
-            key={action.tooltip ? undefined : index}
-          >
-            <ActionIcon iconSize="l" />
-          </span>
-        );
-        return action.tooltip ? (
-          <Tooltip
-            id={`${action.label}-tooltip`}
-            title={action.tooltip}
-            placement={action.placement}
-            key={index}
-          >
-            {actionButton}
-          </Tooltip>
-        ) : (
-          actionButton
-        );
-      })}
+      {actions.map((action, index) => (
+        <ActionButton key={action.tooltip ? undefined : index} {...action} />
+      ))}
     </StyledActions>
   );
 }

Reply via email to