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

michaelsmolina 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 d0e80d2079 refactor: Redesigns the Results panel toolbar and enables 
extensions to contribute toolbar actions (#37255)
d0e80d2079 is described below

commit d0e80d2079bf7d5eb646c551141b1498b6838f5f
Author: Michael S. Molina <[email protected]>
AuthorDate: Wed Jan 21 08:49:32 2026 -0300

    refactor: Redesigns the Results panel toolbar and enables extensions to 
contribute toolbar actions (#37255)
---
 .../ExploreResultsButton.test.tsx                  |   8 +-
 .../components/ExploreResultsButton/index.tsx      |  12 +-
 .../src/SqlLab/components/QueryHistory/index.tsx   |   3 +
 .../src/SqlLab/components/ResultSet/index.tsx      | 362 +++++++++-----------
 .../src/SqlLab/components/SaveQuery/index.tsx      |   1 +
 .../src/SqlLab/components/SouthPane/Results.tsx    |   2 +-
 .../src/SqlLab/components/SouthPane/index.tsx      |  18 +-
 .../src/SqlLab/components/SqlEditor/index.tsx      |  24 +-
 .../SqlEditorTopBar/SqlEditorTopBar.test.tsx       |  62 ++--
 .../SqlLab/components/SqlEditorTopBar/index.tsx    |  29 +-
 superset-frontend/src/SqlLab/contributions.ts      |   2 +
 .../MenuListExtension/MenuListExtension.test.tsx   | 374 ---------------------
 .../src/components/MenuListExtension/index.tsx     | 157 ---------
 .../src/components/PanelToolbar/index.tsx          | 165 +++++++++
 14 files changed, 393 insertions(+), 826 deletions(-)

diff --git 
a/superset-frontend/src/SqlLab/components/ExploreResultsButton/ExploreResultsButton.test.tsx
 
b/superset-frontend/src/SqlLab/components/ExploreResultsButton/ExploreResultsButton.test.tsx
index 63283a0862..9a00dda136 100644
--- 
a/superset-frontend/src/SqlLab/components/ExploreResultsButton/ExploreResultsButton.test.tsx
+++ 
b/superset-frontend/src/SqlLab/components/ExploreResultsButton/ExploreResultsButton.test.tsx
@@ -34,18 +34,14 @@ const setup = (
 // eslint-disable-next-line no-restricted-globals -- TODO: Migrate from 
describe blocks
 describe('ExploreResultsButton', () => {
   test('renders', async () => {
-    const { queryByText } = setup(jest.fn(), {
+    setup(jest.fn(), {
       database: { allows_subquery: true },
     });
-    expect(queryByText('Create chart')).toBeInTheDocument();
-    // Updated line to match the actual button name that includes the icon
     expect(screen.getByRole('button', { name: /Create chart/i 
})).toBeEnabled();
   });
 
   test('renders disabled if subquery not allowed', async () => {
-    const { queryByText } = setup(jest.fn());
-    expect(queryByText('Create chart')).toBeInTheDocument();
-    // Updated line to match the actual button name that includes the icon
+    setup(jest.fn());
     expect(
       screen.getByRole('button', { name: /Create chart/i }),
     ).toBeDisabled();
diff --git 
a/superset-frontend/src/SqlLab/components/ExploreResultsButton/index.tsx 
b/superset-frontend/src/SqlLab/components/ExploreResultsButton/index.tsx
index c485302510..39deb485b2 100644
--- a/superset-frontend/src/SqlLab/components/ExploreResultsButton/index.tsx
+++ b/superset-frontend/src/SqlLab/components/ExploreResultsButton/index.tsx
@@ -38,16 +38,16 @@ const ExploreResultsButton = ({
   return (
     <Button
       buttonSize="small"
-      buttonStyle="secondary"
-      icon={<Icons.LineChartOutlined />}
+      variant="text"
+      color="primary"
+      icon={<Icons.LineChartOutlined iconSize="m" />}
       onClick={onClick}
       disabled={!allowsSubquery}
       role="button"
-      tooltip={t('Explore the result set in the data exploration view')}
+      tooltip={t('Create chart')}
+      aria-label={t('Create chart')}
       data-test="explore-results-button"
-    >
-      {t('Create chart')}
-    </Button>
+    />
   );
 };
 
diff --git a/superset-frontend/src/SqlLab/components/QueryHistory/index.tsx 
b/superset-frontend/src/SqlLab/components/QueryHistory/index.tsx
index d3e37a3586..b8c85f0628 100644
--- a/superset-frontend/src/SqlLab/components/QueryHistory/index.tsx
+++ b/superset-frontend/src/SqlLab/components/QueryHistory/index.tsx
@@ -29,6 +29,8 @@ import { SqlLabRootState } from 'src/SqlLab/types';
 import { useEditorQueriesQuery } from 'src/hooks/apiResources/queries';
 import useEffectEvent from 'src/hooks/useEffectEvent';
 import useQueryEditor from 'src/SqlLab/hooks/useQueryEditor';
+import PanelToolbar from 'src/components/PanelToolbar';
+import { ViewContribution } from 'src/SqlLab/contributions';
 
 interface QueryHistoryProps {
   queryEditorId: string | number;
@@ -119,6 +121,7 @@ const QueryHistory = ({
 
   return editorQueries.length > 0 ? (
     <>
+      <PanelToolbar viewId={ViewContribution.QueryHistory} />
       <QueryTable
         columns={[
           'state',
diff --git a/superset-frontend/src/SqlLab/components/ResultSet/index.tsx 
b/superset-frontend/src/SqlLab/components/ResultSet/index.tsx
index 439bf37ca2..5229063580 100644
--- a/superset-frontend/src/SqlLab/components/ResultSet/index.tsx
+++ b/superset-frontend/src/SqlLab/components/ResultSet/index.tsx
@@ -32,8 +32,8 @@ import { pick } from 'lodash';
 import {
   Button,
   ButtonGroup,
+  Divider,
   Tooltip,
-  Card,
   Input,
   Label,
   Loading,
@@ -91,6 +91,8 @@ import ExploreCtasResultsButton from 
'../ExploreCtasResultsButton';
 import ExploreResultsButton from '../ExploreResultsButton';
 import HighlightedSql from '../HighlightedSql';
 import QueryStateLabel from '../QueryStateLabel';
+import PanelToolbar from 'src/components/PanelToolbar';
+import { ViewContribution } from 'src/SqlLab/contributions';
 
 enum LimitingFactor {
   Query = 'QUERY',
@@ -147,29 +149,11 @@ const ReturnedRows = styled.div`
   line-height: 1;
 `;
 
-const ResultSetControls = styled.div`
-  display: flex;
-  justify-content: space-between;
-`;
-
 const ResultSetButtons = styled.div`
   display: grid;
   grid-auto-flow: column;
-  padding-right: ${({ theme }) => 2 * theme.sizeUnit}px;
-`;
-
-const CopyStyledButton = styled(Button)`
-  &:hover {
-    color: ${({ theme }) => theme.colorPrimary};
-    text-decoration: unset;
-  }
-
-  span > :first-of-type {
-    margin: 0;
-  }
 `;
 
-const ROWS_CHIP_WIDTH = 100;
 const GAP = 8;
 
 const extensionsRegistry = getExtensionsRegistry();
@@ -389,8 +373,71 @@ const ResultSet = ({
         }
       };
 
+      const defaultPrimaryActions = (
+        <>
+          {visualize && database?.allows_virtual_table_explore && (
+            <ExploreResultsButton
+              database={database}
+              onClick={createExploreResultsOnClick}
+            />
+          )}
+          {csv && canExportData && (
+            <Button
+              buttonSize="small"
+              variant="text"
+              color="primary"
+              icon={<Icons.DownloadOutlined iconSize="m" />}
+              tooltip={t('Download to CSV')}
+              aria-label={t('Download to CSV')}
+              {...(!shouldUseStreamingExport() && {
+                href: getExportCsvUrl(query.id),
+              })}
+              data-test="export-csv-button"
+              onClick={e => {
+                const useStreaming = shouldUseStreamingExport();
+
+                if (useStreaming) {
+                  e.preventDefault();
+                  setShowStreamingModal(true);
+
+                  startExport({
+                    url: makeUrl('/api/v1/sqllab/export_streaming/'),
+                    payload: { client_id: query.id },
+                    exportType: 'csv',
+                    expectedRows: rows,
+                  });
+                } else {
+                  handleDownloadCsv(e);
+                }
+              }}
+            />
+          )}
+          {canExportData && (
+            <CopyToClipboard
+              text={prepareCopyToClipboardTabularData(data, columns)}
+              wrapped={false}
+              copyNode={
+                <Button
+                  buttonSize="small"
+                  variant="text"
+                  color="primary"
+                  icon={<Icons.CopyOutlined iconSize="m" />}
+                  tooltip={t('Copy to Clipboard')}
+                  aria-label={t('Copy to Clipboard')}
+                  data-test="copy-to-clipboard-button"
+                />
+              }
+              hideTooltip
+              onCopyEnd={() =>
+                logAction(LOG_ACTIONS_SQLLAB_COPY_RESULT_TO_CLIPBOARD, {})
+              }
+            />
+          )}
+        </>
+      );
+
       return (
-        <ResultSetControls>
+        <ResultSetButtons>
           <SaveDatasetModal
             visible={showSaveDatasetModal}
             onHide={() => setShowSaveDatasetModal(false)}
@@ -401,98 +448,21 @@ const ResultSet = ({
             )}
             datasource={datasource}
           />
-          <ResultSetButtons>
-            {visualize && database?.allows_virtual_table_explore && (
-              <ExploreResultsButton
-                database={database}
-                onClick={createExploreResultsOnClick}
-              />
-            )}
-            {csv && canExportData && (
-              <CopyStyledButton
-                buttonSize="small"
-                buttonStyle="secondary"
-                {...(!shouldUseStreamingExport() && {
-                  href: getExportCsvUrl(query.id),
-                })}
-                data-test="export-csv-button"
-                onClick={e => {
-                  const useStreaming = shouldUseStreamingExport();
-
-                  if (useStreaming) {
-                    e.preventDefault();
-                    setShowStreamingModal(true);
-
-                    startExport({
-                      url: makeUrl('/api/v1/sqllab/export_streaming/'),
-                      payload: { client_id: query.id },
-                      exportType: 'csv',
-                      expectedRows: rows,
-                    });
-                  } else {
-                    handleDownloadCsv(e);
-                  }
-                }}
-              >
-                <Icons.DownloadOutlined iconSize="m" /> {t('Download to CSV')}
-              </CopyStyledButton>
-            )}
-            {canExportData && (
-              <CopyToClipboard
-                text={prepareCopyToClipboardTabularData(data, columns)}
-                wrapped={false}
-                copyNode={
-                  <CopyStyledButton
-                    buttonSize="small"
-                    buttonStyle="secondary"
-                    data-test="copy-to-clipboard-button"
-                  >
-                    <Icons.CopyOutlined iconSize="s" /> {t('Copy to 
Clipboard')}
-                  </CopyStyledButton>
-                }
-                hideTooltip
-                onCopyEnd={() =>
-                  logAction(LOG_ACTIONS_SQLLAB_COPY_RESULT_TO_CLIPBOARD, {})
-                }
-              />
-            )}
-          </ResultSetButtons>
-          {search && (
-            <Input
-              onChange={changeSearch}
-              value={searchText}
-              className="form-control input-sm"
-              placeholder={t('Filter results')}
-            />
-          )}
-        </ResultSetControls>
+          <PanelToolbar
+            viewId={ViewContribution.Results}
+            defaultPrimaryActions={defaultPrimaryActions}
+          />
+        </ResultSetButtons>
       );
     }
-    return <div />;
+    return null;
   };
 
-  const renderRowsReturned = (alertMessage: boolean) => {
+  const renderRowsReturned = () => {
     const { results, rows, queryLimit, limitingFactor } = query;
     let limitMessage = '';
     const limitReached = results?.displayLimitReached;
     const limit = queryLimit || results.query.limit;
-    const isAdmin = !!user?.roles?.Admin;
-    const rowsCount = Math.min(rows || 0, results?.data?.length || 0);
-
-    const displayMaxRowsReachedMessage = {
-      withAdmin: t(
-        'The number of results displayed is limited to %(rows)d by the 
configuration DISPLAY_MAX_ROW. ' +
-          'Please add additional limits/filters or download to csv to see more 
rows up to ' +
-          'the %(limit)d limit.',
-        { rows: rowsCount, limit },
-      ),
-      withoutAdmin: t(
-        'The number of results displayed is limited to %(rows)d. ' +
-          'Please add additional limits/filters, download to csv, or contact 
an admin ' +
-          'to see more rows up to the %(limit)d limit.',
-        { rows: rowsCount, limit },
-      ),
-    };
     const shouldUseDefaultDropdownAlert =
       limit === defaultQueryLimit && limitingFactor === 
LimitingFactor.Dropdown;
 
@@ -514,76 +484,53 @@ const ResultSet = ({
         'The number of rows displayed is limited to %(rows)d by the query and 
limit dropdown.',
         { rows },
       );
+    } else if (shouldUseDefaultDropdownAlert) {
+      limitMessage = t(
+        'The number of rows displayed is limited to %(rows)d by the dropdown.',
+        { rows },
+      );
+    } else if (limitReached) {
+      limitMessage = t(
+        'The number of results displayed is limited to %(rows)d.',
+        { rows },
+      );
     }
+
     const formattedRowCount = getNumberFormatter()(rows);
     const rowsReturnedMessage = t('%(rows)d rows returned', {
       rows,
     });
 
-    const tooltipText = `${rowsReturnedMessage}. ${limitMessage}`;
-
-    if (alertMessage) {
-      return (
-        <>
-          {!limitReached && shouldUseDefaultDropdownAlert && (
-            <div>
-              <Alert
-                closable
-                type="warning"
-                message={t(
-                  'The number of rows displayed is limited to %(rows)d by the 
dropdown.',
-                  { rows },
-                )}
-              />
-            </div>
-          )}
-          {limitReached && (
-            <div>
-              <Alert
-                closable
-                type="warning"
-                message={
-                  isAdmin
-                    ? displayMaxRowsReachedMessage.withAdmin
-                    : displayMaxRowsReachedMessage.withoutAdmin
-                }
-              />
-            </div>
-          )}
-        </>
-      );
-    }
-    const showRowsReturned =
-      showSqlInline || (!limitReached && !shouldUseDefaultDropdownAlert);
+    const hasWarning = !!limitMessage;
+    const tooltipText = hasWarning
+      ? `${rowsReturnedMessage}. ${limitMessage}`
+      : rowsReturnedMessage;
 
     return (
-      <>
-        {showRowsReturned && (
-          <ReturnedRows>
-            <Tooltip
-              id="sqllab-rowcount-tooltip"
-              title={tooltipText}
-              placement="left"
-            >
-              <Label
+      <ReturnedRows>
+        <Tooltip
+          id="sqllab-rowcount-tooltip"
+          title={tooltipText}
+          placement="left"
+        >
+          <Label
+            css={css`
+              line-height: ${theme.fontSizeLG}px;
+            `}
+          >
+            {hasWarning && (
+              <Icons.WarningOutlined
                 css={css`
-                  line-height: ${theme.fontSizeLG}px;
+                  font-size: ${theme.fontSize}px;
+                  margin-right: ${theme.sizeUnit}px;
+                  color: ${theme.colorWarning};
                 `}
-              >
-                {limitMessage && (
-                  <Icons.ExclamationCircleOutlined
-                    css={css`
-                      font-size: ${theme.fontSize}px;
-                      margin-right: ${theme.sizeUnit}px;
-                    `}
-                  />
-                )}
-                {tn('%s row', '%s rows', rows, formattedRowCount)}
-              </Label>
-            </Tooltip>
-          </ReturnedRows>
-        )}
-      </>
+              />
+            )}
+            {tn('%s row', '%s rows', rows, formattedRowCount)}
+          </Label>
+        </Tooltip>
+      </ReturnedRows>
     );
   };
 
@@ -728,45 +675,58 @@ const ResultSet = ({
       return (
         <>
           <ResultContainer>
-            {renderControls()}
-            {showSql && showSqlInline ? (
-              <>
-                <div
-                  css={css`
-                    display: flex;
-                    justify-content: space-between;
-                    align-items: center;
-                    gap: ${GAP}px;
-                  `}
-                >
-                  <Card
-                    css={[
-                      css`
-                        height: 28px;
-                        width: calc(100% - ${ROWS_CHIP_WIDTH + GAP}px);
-                        code {
-                          width: 100%;
-                          overflow: hidden;
-                          white-space: nowrap !important;
-                          text-overflow: ellipsis;
-                          display: block;
-                        }
-                      `,
-                    ]}
+            <div
+              css={css`
+                display: flex;
+                align-items: center;
+                gap: ${GAP}px;
+
+                & .ant-divider {
+                  height: ${theme.sizeUnit * 6}px;
+                  margin: 0 ${theme.sizeUnit * 2}px 0 0;
+                }
+              `}
+            >
+              {renderControls()}
+              <Divider type="vertical" />
+              {showSql && (
+                <>
+                  <div
+                    css={css`
+                      flex: 0 1 auto;
+                      min-width: 0;
+                      overflow: hidden;
+                      margin-right: ${theme.sizeUnit}px;
+
+                      & * {
+                        overflow: hidden !important;
+                        white-space: nowrap !important;
+                        text-overflow: ellipsis !important;
+                      }
+
+                      pre {
+                        margin: 0 !important;
+                      }
+                    `}
                   >
                     {sql}
-                  </Card>
-                  {renderRowsReturned(false)}
-                </div>
-                {renderRowsReturned(true)}
-              </>
-            ) : (
-              <>
-                {renderRowsReturned(false)}
-                {renderRowsReturned(true)}
-                {sql}
-              </>
-            )}
+                  </div>
+                  <Divider type="vertical" />
+                </>
+              )}
+              {renderRowsReturned()}
+              {search && (
+                <Input
+                  css={css`
+                    flex: none;
+                    width: 200px;
+                  `}
+                  onChange={changeSearch}
+                  value={searchText}
+                  placeholder={t('Filter results')}
+                />
+              )}
+            </div>
             {useFixedHeight && height !== undefined ? (
               <ResultTable {...tableProps} height={height} />
             ) : (
diff --git a/superset-frontend/src/SqlLab/components/SaveQuery/index.tsx 
b/superset-frontend/src/SqlLab/components/SaveQuery/index.tsx
index 96e025ac48..6d46c4cd78 100644
--- a/superset-frontend/src/SqlLab/components/SaveQuery/index.tsx
+++ b/superset-frontend/src/SqlLab/components/SaveQuery/index.tsx
@@ -62,6 +62,7 @@ export type QueryPayload = {
 } & Pick<QueryEditor, 'dbId' | 'catalog' | 'schema' | 'sql'>;
 
 const Styles = styled.span`
+  display: contents;
   span[role='img']:not([aria-label='down']) {
     display: flex;
     margin: 0;
diff --git a/superset-frontend/src/SqlLab/components/SouthPane/Results.tsx 
b/superset-frontend/src/SqlLab/components/SouthPane/Results.tsx
index 661efb7171..2f34149815 100644
--- a/superset-frontend/src/SqlLab/components/SouthPane/Results.tsx
+++ b/superset-frontend/src/SqlLab/components/SouthPane/Results.tsx
@@ -80,7 +80,7 @@ const Results: FC<Props> = ({
   ) {
     return (
       <Alert
-        type="warning"
+        type="info"
         message={t('No stored results found, you need to re-run your query')}
       />
     );
diff --git a/superset-frontend/src/SqlLab/components/SouthPane/index.tsx 
b/superset-frontend/src/SqlLab/components/SouthPane/index.tsx
index 1f4697db6b..4478e716bd 100644
--- a/superset-frontend/src/SqlLab/components/SouthPane/index.tsx
+++ b/superset-frontend/src/SqlLab/components/SouthPane/index.tsx
@@ -29,7 +29,7 @@ import { Flex, Label } from '@superset-ui/core/components';
 import { Icons } from '@superset-ui/core/components/Icons';
 import { SqlLabRootState } from 'src/SqlLab/types';
 import { ViewContribution } from 'src/SqlLab/contributions';
-import MenuListExtension from 'src/components/MenuListExtension';
+import PanelToolbar from 'src/components/PanelToolbar';
 import { useExtensionsContext } from 'src/extensions/ExtensionsContext';
 import ExtensionsManager from 'src/extensions/ExtensionsManager';
 import useQueryEditor from 'src/SqlLab/hooks/useQueryEditor';
@@ -215,7 +215,18 @@ const SouthPane = ({
     ...contributions.map(contribution => ({
       key: contribution.id,
       label: contribution.name,
-      children: getView(contribution.id),
+      children: (
+        <div
+          css={css`
+            & > div:first-of-type {
+              padding-bottom: ${theme.sizeUnit * 2}px;
+            }
+          `}
+        >
+          <PanelToolbar viewId={contribution.id} />
+          {getView(contribution.id)}
+        </div>
+      ),
       forceRender: true,
       closable: false,
     })),
@@ -231,8 +242,7 @@ const SouthPane = ({
                 padding: 8px;
               `}
             >
-              <MenuListExtension viewId={ViewContribution.Panels} primary />
-              <MenuListExtension viewId={ViewContribution.Panels} secondary />
+              <PanelToolbar viewId={ViewContribution.Panels} />
             </Flex>
           ),
         }}
diff --git a/superset-frontend/src/SqlLab/components/SqlEditor/index.tsx 
b/superset-frontend/src/SqlLab/components/SqlEditor/index.tsx
index 5fe4f5367d..da2caaf006 100644
--- a/superset-frontend/src/SqlLab/components/SqlEditor/index.tsx
+++ b/superset-frontend/src/SqlLab/components/SqlEditor/index.tsx
@@ -760,23 +760,19 @@ const SqlEditor: FC<Props> = ({
           stopQuery={stopQuery}
           overlayCreateAsMenu={showMenu ? runMenuBtn : null}
         />
-        <span>
-          <QueryLimitSelect
-            queryEditorId={queryEditor.id}
-            maxRow={maxRow}
-            defaultQueryLimit={defaultQueryLimit}
-          />
-        </span>
+        <QueryLimitSelect
+          queryEditorId={queryEditor.id}
+          maxRow={maxRow}
+          defaultQueryLimit={defaultQueryLimit}
+        />
         <Divider type="vertical" />
         {isFeatureEnabled(FeatureFlag.EstimateQueryCost) &&
           database?.allows_cost_estimate && (
-            <span>
-              <EstimateQueryCostButton
-                getEstimate={getQueryCostEstimate}
-                queryEditorId={queryEditor.id}
-                tooltip={t('Estimate the cost before running a query')}
-              />
-            </span>
+            <EstimateQueryCostButton
+              getEstimate={getQueryCostEstimate}
+              queryEditorId={queryEditor.id}
+              tooltip={t('Estimate the cost before running a query')}
+            />
           )}
         <SaveQuery
           queryEditorId={queryEditor.id}
diff --git 
a/superset-frontend/src/SqlLab/components/SqlEditorTopBar/SqlEditorTopBar.test.tsx
 
b/superset-frontend/src/SqlLab/components/SqlEditorTopBar/SqlEditorTopBar.test.tsx
index d5a6871d52..224627b070 100644
--- 
a/superset-frontend/src/SqlLab/components/SqlEditorTopBar/SqlEditorTopBar.test.tsx
+++ 
b/superset-frontend/src/SqlLab/components/SqlEditorTopBar/SqlEditorTopBar.test.tsx
@@ -22,29 +22,23 @@ import SqlEditorTopBar, {
   SqlEditorTopBarProps,
 } from 'src/SqlLab/components/SqlEditorTopBar';
 
-jest.mock('src/components/MenuListExtension', () => ({
+jest.mock('src/components/PanelToolbar', () => ({
   __esModule: true,
   default: ({
-    children,
     viewId,
-    primary,
-    secondary,
-    defaultItems,
+    defaultPrimaryActions,
+    defaultSecondaryActions,
   }: {
-    children?: React.ReactNode;
     viewId: string;
-    primary?: boolean;
-    secondary?: boolean;
-    defaultItems?: MenuItemType[];
+    defaultPrimaryActions?: React.ReactNode;
+    defaultSecondaryActions?: MenuItemType[];
   }) => (
     <div
-      data-test="mock-menu-extension"
+      data-test="mock-panel-toolbar"
       data-view-id={viewId}
-      data-primary={primary}
-      data-secondary={secondary}
-      data-default-items-count={defaultItems?.length ?? 0}
+      data-default-secondary-count={defaultSecondaryActions?.length ?? 0}
     >
-      {children}
+      {defaultPrimaryActions}
     </div>
   ),
 }));
@@ -63,30 +57,23 @@ const setup = (props?: Partial<SqlEditorTopBarProps>) =>
 
 test('renders SqlEditorTopBar component', () => {
   setup();
-  const menuExtensions = screen.getAllByTestId('mock-menu-extension');
-  expect(menuExtensions).toHaveLength(2);
+  const panelToolbar = screen.getByTestId('mock-panel-toolbar');
+  expect(panelToolbar).toBeInTheDocument();
 });
 
-test('renders primary MenuListExtension with correct props', () => {
+test('renders PanelToolbar with correct viewId', () => {
   setup();
-  const menuExtensions = screen.getAllByTestId('mock-menu-extension');
-  const primaryExtension = menuExtensions[0];
-
-  expect(primaryExtension).toHaveAttribute('data-view-id', 'sqllab.editor');
-  expect(primaryExtension).toHaveAttribute('data-primary', 'true');
+  const panelToolbar = screen.getByTestId('mock-panel-toolbar');
+  expect(panelToolbar).toHaveAttribute('data-view-id', 'sqllab.editor');
 });
 
-test('renders secondary MenuListExtension with correct props', () => {
+test('renders PanelToolbar with correct secondary actions count', () => {
   setup();
-  const menuExtensions = screen.getAllByTestId('mock-menu-extension');
-  const secondaryExtension = menuExtensions[1];
-
-  expect(secondaryExtension).toHaveAttribute('data-view-id', 'sqllab.editor');
-  expect(secondaryExtension).toHaveAttribute('data-secondary', 'true');
-  expect(secondaryExtension).toHaveAttribute('data-default-items-count', '2');
+  const panelToolbar = screen.getByTestId('mock-panel-toolbar');
+  expect(panelToolbar).toHaveAttribute('data-default-secondary-count', '2');
 });
 
-test('renders defaultPrimaryActions as children of primary MenuListExtension', 
() => {
+test('renders defaultPrimaryActions', () => {
   setup();
   expect(
     screen.getByRole('button', { name: 'Primary Action' }),
@@ -114,17 +101,6 @@ test('renders with custom primary actions', () => {
 test('renders with empty secondary actions', () => {
   setup({ defaultSecondaryActions: [] });
 
-  const menuExtensions = screen.getAllByTestId('mock-menu-extension');
-  const secondaryExtension = menuExtensions[1];
-
-  expect(secondaryExtension).toHaveAttribute('data-default-items-count', '0');
-});
-
-test('passes correct viewId (ViewContribution.Editor) to MenuListExtension', 
() => {
-  setup();
-  const menuExtensions = screen.getAllByTestId('mock-menu-extension');
-
-  menuExtensions.forEach(extension => {
-    expect(extension).toHaveAttribute('data-view-id', 'sqllab.editor');
-  });
+  const panelToolbar = screen.getByTestId('mock-panel-toolbar');
+  expect(panelToolbar).toHaveAttribute('data-default-secondary-count', '0');
 });
diff --git a/superset-frontend/src/SqlLab/components/SqlEditorTopBar/index.tsx 
b/superset-frontend/src/SqlLab/components/SqlEditorTopBar/index.tsx
index f8b7ead860..0a3f00da3f 100644
--- a/superset-frontend/src/SqlLab/components/SqlEditorTopBar/index.tsx
+++ b/superset-frontend/src/SqlLab/components/SqlEditorTopBar/index.tsx
@@ -16,25 +16,21 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { Divider, Flex } from '@superset-ui/core/components';
+import { Flex } from '@superset-ui/core/components';
 import { styled } from '@apache-superset/core/ui';
+import { MenuItemType } from '@superset-ui/core/components/Menu';
 import { ViewContribution } from 'src/SqlLab/contributions';
-import MenuListExtension, {
-  type MenuListExtensionProps,
-} from 'src/components/MenuListExtension';
+import PanelToolbar from 'src/components/PanelToolbar';
 
 const StyledFlex = styled(Flex)`
   margin-bottom: ${({ theme }) => theme.sizeUnit * 2}px;
-
-  & .ant-divider {
-    margin: ${({ theme }) => theme.sizeUnit * 2}px 0;
-    height: ${({ theme }) => theme.sizeUnit * 6}px;
-  }
+  padding: ${({ theme }) => theme.sizeUnit}px 0;
 `;
+
 export interface SqlEditorTopBarProps {
   queryEditorId: string;
   defaultPrimaryActions: React.ReactNode;
-  defaultSecondaryActions: MenuListExtensionProps['defaultItems'];
+  defaultSecondaryActions: MenuItemType[];
 }
 
 const SqlEditorTopBar = ({
@@ -43,18 +39,11 @@ const SqlEditorTopBar = ({
 }: SqlEditorTopBarProps) => (
   <StyledFlex justify="space-between" gap="small" id="js-sql-toolbar">
     <Flex flex={1} gap="small" align="center">
-      <Flex gap="small" align="center">
-        <MenuListExtension viewId={ViewContribution.Editor} primary 
compactMode>
-          {defaultPrimaryActions}
-        </MenuListExtension>
-      </Flex>
-      <Divider type="vertical" />
-      <MenuListExtension
+      <PanelToolbar
         viewId={ViewContribution.Editor}
-        secondary
-        defaultItems={defaultSecondaryActions}
+        defaultPrimaryActions={defaultPrimaryActions}
+        defaultSecondaryActions={defaultSecondaryActions}
       />
-      <Divider type="vertical" />
     </Flex>
   </StyledFlex>
 );
diff --git a/superset-frontend/src/SqlLab/contributions.ts 
b/superset-frontend/src/SqlLab/contributions.ts
index b9549bed28..bdaf184e32 100644
--- a/superset-frontend/src/SqlLab/contributions.ts
+++ b/superset-frontend/src/SqlLab/contributions.ts
@@ -21,4 +21,6 @@ export enum ViewContribution {
   Panels = 'sqllab.panels',
   Editor = 'sqllab.editor',
   StatusBar = 'sqllab.statusBar',
+  Results = 'sqllab.results',
+  QueryHistory = 'sqllab.queryHistory',
 }
diff --git 
a/superset-frontend/src/components/MenuListExtension/MenuListExtension.test.tsx 
b/superset-frontend/src/components/MenuListExtension/MenuListExtension.test.tsx
deleted file mode 100644
index 853c58c8aa..0000000000
--- 
a/superset-frontend/src/components/MenuListExtension/MenuListExtension.test.tsx
+++ /dev/null
@@ -1,374 +0,0 @@
-/**
- * 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, waitFor } from 'spec/helpers/testing-library';
-import userEvent from '@testing-library/user-event';
-import type { contributions, core } from '@apache-superset/core';
-import ExtensionsManager from 'src/extensions/ExtensionsManager';
-import { commands } from 'src/core';
-import MenuListExtension from '.';
-
-jest.mock('src/core', () => ({
-  commands: {
-    executeCommand: jest.fn(),
-  },
-}));
-
-function createMockCommand(
-  command: string,
-  overrides: Partial<contributions.CommandContribution> = {},
-): contributions.CommandContribution {
-  return {
-    command,
-    icon: 'PlusOutlined',
-    title: `${command} Title`,
-    description: `${command} description`,
-    ...overrides,
-  };
-}
-
-function createMockMenuItem(
-  view: string,
-  command: string,
-): contributions.MenuItem {
-  return {
-    view,
-    command,
-  };
-}
-
-function createMockMenu(
-  overrides: Partial<contributions.MenuContribution> = {},
-): contributions.MenuContribution {
-  return {
-    context: [],
-    primary: [],
-    secondary: [],
-    ...overrides,
-  };
-}
-
-function createMockExtension(
-  options: Partial<core.Extension> & {
-    commands?: contributions.CommandContribution[];
-    menus?: Record<string, contributions.MenuContribution>;
-  } = {},
-): core.Extension {
-  const {
-    id = 'test-extension',
-    name = 'Test Extension',
-    commands: cmds = [],
-    menus = {},
-  } = options;
-
-  return {
-    id,
-    name,
-    description: 'A test extension',
-    version: '1.0.0',
-    dependencies: [],
-    remoteEntry: '',
-    exposedModules: [],
-    extensionDependencies: [],
-    contributions: {
-      commands: cmds,
-      menus,
-      views: {},
-    },
-    activate: jest.fn(),
-    deactivate: jest.fn(),
-  };
-}
-
-function setupActivatedExtension(
-  manager: ExtensionsManager,
-  extension: core.Extension,
-) {
-  const context = { disposables: [] };
-  (manager as any).contextIndex.set(extension.id, context);
-  (manager as any).extensionContributions.set(extension.id, {
-    commands: extension.contributions.commands,
-    menus: extension.contributions.menus,
-    views: extension.contributions.views,
-  });
-}
-
-async function createActivatedExtension(
-  manager: ExtensionsManager,
-  extensionOptions: Parameters<typeof createMockExtension>[0] = {},
-): Promise<core.Extension> {
-  const mockExtension = createMockExtension(extensionOptions);
-  await manager.initializeExtension(mockExtension);
-  setupActivatedExtension(manager, mockExtension);
-  return mockExtension;
-}
-
-const TEST_VIEW_ID = 'test.menu';
-
-beforeEach(() => {
-  (ExtensionsManager as any).instance = undefined;
-  jest.clearAllMocks();
-});
-
-afterEach(() => {
-  (ExtensionsManager as any).instance = undefined;
-});
-
-test('renders children when primary mode with no extensions', () => {
-  render(
-    <MenuListExtension viewId={TEST_VIEW_ID} primary>
-      <button type="button">Child Button</button>
-    </MenuListExtension>,
-  );
-
-  expect(
-    screen.getByRole('button', { name: 'Child Button' }),
-  ).toBeInTheDocument();
-});
-
-test('renders primary actions from extension contributions', async () => {
-  const manager = ExtensionsManager.getInstance();
-
-  await createActivatedExtension(manager, {
-    commands: [createMockCommand('test.action')],
-    menus: {
-      [TEST_VIEW_ID]: createMockMenu({
-        primary: [createMockMenuItem('test-view', 'test.action')],
-      }),
-    },
-  });
-
-  render(<MenuListExtension viewId={TEST_VIEW_ID} primary />);
-
-  expect(screen.getByText('test.action Title')).toBeInTheDocument();
-});
-
-test('renders primary actions with children', async () => {
-  const manager = ExtensionsManager.getInstance();
-
-  await createActivatedExtension(manager, {
-    commands: [createMockCommand('test.action')],
-    menus: {
-      [TEST_VIEW_ID]: createMockMenu({
-        primary: [createMockMenuItem('test-view', 'test.action')],
-      }),
-    },
-  });
-
-  render(
-    <MenuListExtension viewId={TEST_VIEW_ID} primary>
-      <button type="button">Child Button</button>
-    </MenuListExtension>,
-  );
-
-  expect(screen.getByText('test.action Title')).toBeInTheDocument();
-  expect(
-    screen.getByRole('button', { name: 'Child Button' }),
-  ).toBeInTheDocument();
-});
-
-test('hides title in compact mode for primary actions', async () => {
-  const manager = ExtensionsManager.getInstance();
-
-  await createActivatedExtension(manager, {
-    commands: [createMockCommand('test.action')],
-    menus: {
-      [TEST_VIEW_ID]: createMockMenu({
-        primary: [createMockMenuItem('test-view', 'test.action')],
-      }),
-    },
-  });
-
-  render(<MenuListExtension viewId={TEST_VIEW_ID} primary compactMode />);
-
-  expect(screen.queryByText('test.action Title')).not.toBeInTheDocument();
-  expect(screen.getByRole('button')).toBeInTheDocument();
-});
-
-test('executes command when primary action button is clicked', async () => {
-  const manager = ExtensionsManager.getInstance();
-
-  await createActivatedExtension(manager, {
-    commands: [createMockCommand('test.action')],
-    menus: {
-      [TEST_VIEW_ID]: createMockMenu({
-        primary: [createMockMenuItem('test-view', 'test.action')],
-      }),
-    },
-  });
-
-  render(<MenuListExtension viewId={TEST_VIEW_ID} primary />);
-
-  const button = screen.getByRole('button', { name: 'test.action Title' });
-  await userEvent.click(button);
-
-  expect(commands.executeCommand).toHaveBeenCalledWith('test.action');
-});
-
-test('returns null when secondary mode with no actions and no defaultItems', 
() => {
-  const { container } = render(
-    <MenuListExtension viewId={TEST_VIEW_ID} secondary />,
-  );
-
-  expect(container).toBeEmptyDOMElement();
-});
-
-test('renders dropdown button when secondary mode with defaultItems', () => {
-  render(
-    <MenuListExtension
-      viewId={TEST_VIEW_ID}
-      secondary
-      defaultItems={[{ key: 'item1', label: 'Item 1' }]}
-    />,
-  );
-
-  expect(screen.getByRole('button')).toBeInTheDocument();
-});
-
-test('renders dropdown menu with defaultItems when clicked', async () => {
-  render(
-    <MenuListExtension
-      viewId={TEST_VIEW_ID}
-      secondary
-      defaultItems={[
-        { key: 'item1', label: 'Item 1' },
-        { key: 'item2', label: 'Item 2' },
-      ]}
-    />,
-  );
-
-  const dropdownButton = screen.getByRole('button');
-  await userEvent.click(dropdownButton);
-
-  await waitFor(() => {
-    expect(screen.getByText('Item 1')).toBeInTheDocument();
-    expect(screen.getByText('Item 2')).toBeInTheDocument();
-  });
-});
-
-test('renders secondary actions from extension contributions', async () => {
-  const manager = ExtensionsManager.getInstance();
-
-  await createActivatedExtension(manager, {
-    commands: [createMockCommand('test.secondary')],
-    menus: {
-      [TEST_VIEW_ID]: createMockMenu({
-        secondary: [createMockMenuItem('test-view', 'test.secondary')],
-      }),
-    },
-  });
-
-  render(<MenuListExtension viewId={TEST_VIEW_ID} secondary />);
-
-  const dropdownButton = screen.getByRole('button');
-  await userEvent.click(dropdownButton);
-
-  await waitFor(() => {
-    expect(screen.getByText('test.secondary Title')).toBeInTheDocument();
-  });
-});
-
-test('merges extension secondary actions with defaultItems', async () => {
-  const manager = ExtensionsManager.getInstance();
-
-  await createActivatedExtension(manager, {
-    commands: [createMockCommand('test.secondary')],
-    menus: {
-      [TEST_VIEW_ID]: createMockMenu({
-        secondary: [createMockMenuItem('test-view', 'test.secondary')],
-      }),
-    },
-  });
-
-  render(
-    <MenuListExtension
-      viewId={TEST_VIEW_ID}
-      secondary
-      defaultItems={[{ key: 'default-item', label: 'Default Item' }]}
-    />,
-  );
-
-  const dropdownButton = screen.getByRole('button');
-  await userEvent.click(dropdownButton);
-
-  await waitFor(() => {
-    expect(screen.getByText('test.secondary Title')).toBeInTheDocument();
-    expect(screen.getByText('Default Item')).toBeInTheDocument();
-  });
-});
-
-test('executes command when secondary menu item is clicked', async () => {
-  const manager = ExtensionsManager.getInstance();
-
-  await createActivatedExtension(manager, {
-    commands: [createMockCommand('test.secondary')],
-    menus: {
-      [TEST_VIEW_ID]: createMockMenu({
-        secondary: [createMockMenuItem('test-view', 'test.secondary')],
-      }),
-    },
-  });
-
-  render(<MenuListExtension viewId={TEST_VIEW_ID} secondary />);
-
-  const dropdownButton = screen.getByRole('button');
-  await userEvent.click(dropdownButton);
-
-  await waitFor(() => {
-    expect(screen.getByText('test.secondary Title')).toBeInTheDocument();
-  });
-
-  const menuItem = screen.getByText('test.secondary Title');
-  await userEvent.click(menuItem);
-
-  expect(commands.executeCommand).toHaveBeenCalledWith('test.secondary');
-});
-
-test('renders multiple primary actions from multiple contributions', async () 
=> {
-  const manager = ExtensionsManager.getInstance();
-
-  await createActivatedExtension(manager, {
-    commands: [
-      createMockCommand('test.action1'),
-      createMockCommand('test.action2'),
-    ],
-    menus: {
-      [TEST_VIEW_ID]: createMockMenu({
-        primary: [
-          createMockMenuItem('test-view1', 'test.action1'),
-          createMockMenuItem('test-view2', 'test.action2'),
-        ],
-      }),
-    },
-  });
-
-  render(<MenuListExtension viewId={TEST_VIEW_ID} primary />);
-
-  expect(await screen.findByText('test.action1 Title')).toBeInTheDocument();
-  expect(screen.getByText('test.action2 Title')).toBeInTheDocument();
-});
-
-test('handles viewId with no matching contributions', () => {
-  render(
-    <MenuListExtension viewId="nonexistent.menu" primary>
-      <button type="button">Fallback</button>
-    </MenuListExtension>,
-  );
-
-  expect(screen.getByRole('button', { name: 'Fallback' })).toBeInTheDocument();
-});
diff --git a/superset-frontend/src/components/MenuListExtension/index.tsx 
b/superset-frontend/src/components/MenuListExtension/index.tsx
deleted file mode 100644
index 98608f6a29..0000000000
--- a/superset-frontend/src/components/MenuListExtension/index.tsx
+++ /dev/null
@@ -1,157 +0,0 @@
-/**
- * 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 { useMemo } from 'react';
-import { css, useTheme } from '@apache-superset/core/ui';
-import { Button, Dropdown } from '@superset-ui/core/components';
-import { Menu, MenuItemType } from '@superset-ui/core/components/Menu';
-import { Icons } from '@superset-ui/core/components/Icons';
-import { commands } from 'src/core';
-import ExtensionsManager from 'src/extensions/ExtensionsManager';
-
-export type MenuListExtensionProps = {
-  viewId: string;
-} & (
-  | {
-      primary: boolean;
-      secondary?: never;
-      children?: React.ReactNode;
-      defaultItems?: never;
-      compactMode?: boolean;
-    }
-  | {
-      primary?: never;
-      secondary: boolean;
-      children?: never;
-      defaultItems?: MenuItemType[];
-      compactMode?: never;
-    }
-);
-
-const MenuListExtension = ({
-  viewId,
-  primary,
-  secondary,
-  defaultItems,
-  children,
-  compactMode,
-}: MenuListExtensionProps) => {
-  const theme = useTheme();
-  const contributions =
-    ExtensionsManager.getInstance().getMenuContributions(viewId);
-
-  const actions = primary ? contributions?.primary : contributions?.secondary;
-  const primaryActions = useMemo(
-    () =>
-      primary
-        ? (actions || []).map(contribution => {
-            const command =
-              ExtensionsManager.getInstance().getCommandContribution(
-                contribution.command,
-              )!;
-            if (!command?.icon) {
-              return null;
-            }
-            const Icon =
-              (Icons as Record<string, typeof Icons.FileOutlined>)[
-                command.icon
-              ] ?? Icons.FileOutlined;
-
-            return (
-              <Button
-                key={contribution.view}
-                onClick={() => commands.executeCommand(command?.command)}
-                tooltip={command?.description ?? command?.title}
-                icon={<Icon iconSize="m" />}
-                buttonSize="small"
-                aria-label={command?.title}
-                {...(compactMode && { variant: 'text', color: 'primary' })}
-              >
-                {!compactMode ? command?.title : undefined}
-              </Button>
-            );
-          })
-        : [],
-    [actions, primary, compactMode],
-  );
-  const secondaryActions = useMemo(
-    () =>
-      secondary
-        ? (actions || [])
-            .map(contribution => {
-              const command =
-                ExtensionsManager.getInstance().getCommandContribution(
-                  contribution.command,
-                )!;
-              if (!command) {
-                return null;
-              }
-              return {
-                key: command.command,
-                label: command.title,
-                title: command.description,
-                onClick: () => commands.executeCommand(command.command),
-              } as MenuItemType;
-            })
-            .concat(...(defaultItems || []))
-            .filter(Boolean)
-        : [],
-    [actions, secondary, defaultItems],
-  );
-
-  if (secondary && secondaryActions.length === 0) {
-    return null;
-  }
-
-  if (secondary) {
-    return (
-      <Dropdown
-        popupRender={() => (
-          <Menu
-            css={css`
-              & .ant-dropdown-menu-title-content > div {
-                gap: ${theme.sizeUnit * 4}px;
-              }
-            `}
-            items={secondaryActions}
-          />
-        )}
-        trigger={['click']}
-      >
-        <Button
-          showMarginRight={false}
-          color="primary"
-          variant="text"
-          css={css`
-            padding: 8px;
-          `}
-        >
-          <Icons.MoreOutlined />
-        </Button>
-      </Dropdown>
-    );
-  }
-  return (
-    <>
-      {primaryActions}
-      {children}
-    </>
-  );
-};
-
-export default MenuListExtension;
diff --git a/superset-frontend/src/components/PanelToolbar/index.tsx 
b/superset-frontend/src/components/PanelToolbar/index.tsx
new file mode 100644
index 0000000000..7f88197811
--- /dev/null
+++ b/superset-frontend/src/components/PanelToolbar/index.tsx
@@ -0,0 +1,165 @@
+/**
+ * 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 { useMemo } from 'react';
+import { css, useTheme } from '@apache-superset/core/ui';
+import { Button, Divider, Dropdown } from '@superset-ui/core/components';
+import { Menu, MenuItemType } from '@superset-ui/core/components/Menu';
+import { Icons } from '@superset-ui/core/components/Icons';
+import { commands } from 'src/core';
+import ExtensionsManager from 'src/extensions/ExtensionsManager';
+
+export interface PanelToolbarProps {
+  viewId: string;
+  defaultPrimaryActions?: React.ReactNode;
+  defaultSecondaryActions?: MenuItemType[];
+}
+
+const PanelToolbar = ({
+  viewId,
+  defaultPrimaryActions,
+  defaultSecondaryActions,
+}: PanelToolbarProps) => {
+  const theme = useTheme();
+  const contributions =
+    ExtensionsManager.getInstance().getMenuContributions(viewId);
+
+  const primaryContributions = contributions?.primary || [];
+  const secondaryContributions = contributions?.secondary || [];
+
+  const extensionPrimaryActions = useMemo(
+    () =>
+      primaryContributions
+        .map(contribution => {
+          const command =
+            ExtensionsManager.getInstance().getCommandContribution(
+              contribution.command,
+            )!;
+          if (!command?.icon) {
+            return null;
+          }
+          const Icon =
+            (Icons as Record<string, typeof Icons.FileOutlined>)[
+              command.icon
+            ] ?? Icons.FileOutlined;
+
+          return (
+            <Button
+              key={contribution.view}
+              onClick={() => commands.executeCommand(command?.command)}
+              tooltip={command?.description ?? command?.title}
+              icon={<Icon iconSize="m" />}
+              buttonSize="small"
+              aria-label={command?.title}
+              variant="text"
+              color="primary"
+            />
+          );
+        })
+        .filter(Boolean),
+    [primaryContributions],
+  );
+
+  const secondaryActions = useMemo(
+    () =>
+      secondaryContributions
+        .map(contribution => {
+          const command =
+            ExtensionsManager.getInstance().getCommandContribution(
+              contribution.command,
+            )!;
+          if (!command) {
+            return null;
+          }
+          return {
+            key: command.command,
+            label: command.title,
+            title: command.description,
+            onClick: () => commands.executeCommand(command.command),
+          } as MenuItemType;
+        })
+        .filter(Boolean)
+        .concat(defaultSecondaryActions || []),
+    [secondaryContributions, defaultSecondaryActions],
+  );
+
+  const hasPrimaryActions =
+    !!defaultPrimaryActions || extensionPrimaryActions.length > 0;
+  const hasSecondaryActions = secondaryActions.length > 0;
+
+  // If no actions at all, render nothing
+  if (!hasPrimaryActions && !hasSecondaryActions) {
+    return null;
+  }
+
+  const toolbarStyles = css`
+    display: flex;
+    align-items: center;
+    gap: ${theme.sizeUnit * 2}px;
+
+    & .ant-divider {
+      height: ${theme.sizeUnit * 6}px;
+      margin: 0;
+    }
+
+    & .superset-button {
+      margin-left: 0 !important;
+      min-width: ${theme.sizeUnit * 8}px;
+    }
+  `;
+
+  return (
+    <div css={toolbarStyles}>
+      {hasPrimaryActions && (
+        <>
+          {defaultPrimaryActions}
+          {extensionPrimaryActions}
+        </>
+      )}
+      {hasPrimaryActions && hasSecondaryActions && <Divider type="vertical" />}
+      {hasSecondaryActions && (
+        <Dropdown
+          popupRender={() => (
+            <Menu
+              css={css`
+                & .ant-dropdown-menu-title-content > div {
+                  gap: ${theme.sizeUnit * 4}px;
+                }
+              `}
+              items={secondaryActions}
+            />
+          )}
+          trigger={['click']}
+        >
+          <Button
+            showMarginRight={false}
+            color="primary"
+            variant="text"
+            css={css`
+              padding: 8px;
+            `}
+          >
+            <Icons.MoreOutlined />
+          </Button>
+        </Dropdown>
+      )}
+    </div>
+  );
+};
+
+export default PanelToolbar;

Reply via email to