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

rusackas pushed a commit to branch mobile-dashboard-support
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 610ca3dc59fe9c4515c83fffb23504b9126ad855
Author: Evan Rusackas <[email protected]>
AuthorDate: Thu Jan 8 19:56:55 2026 -0800

    feat(mobile): add drawer menus for nav and filters on mobile
    
    - Add hamburger menu in global nav that opens a Drawer with menu items
    - Add "Filters" button on mobile that opens a bottom Drawer with FilterBar
    - Replace horizontal menus with mobile-friendly drawer pattern
    
    🤖 Generated with [Claude Code](https://claude.com/claude-code)
    
    Co-Authored-By: Claude Opus 4.5 <[email protected]>
---
 .../DashboardBuilder/DashboardBuilder.tsx          |  44 +++++++-
 superset-frontend/src/features/home/RightMenu.tsx  | 116 ++++++++++++++-------
 2 files changed, 121 insertions(+), 39 deletions(-)

diff --git 
a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx
 
b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx
index 0afe5222e5..6ad97aeead 100644
--- 
a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx
+++ 
b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx
@@ -22,7 +22,13 @@ import { memo, useCallback, useEffect, useMemo, useRef, 
useState } from 'react';
 import { addAlpha, JsonObject, t, useElementOnScreen } from 
'@superset-ui/core';
 import { css, styled, useTheme } from '@apache-superset/core/ui';
 import { useDispatch, useSelector } from 'react-redux';
-import { EmptyState, Grid, Loading } from '@superset-ui/core/components';
+import {
+  Button,
+  Drawer,
+  EmptyState,
+  Grid,
+  Loading,
+} from '@superset-ui/core/components';
 import { ErrorBoundary, BasicErrorAlert } from 'src/components';
 import BuilderComponentPane from 
'src/dashboard/components/BuilderComponentPane';
 import DashboardHeader from 'src/dashboard/components/Header';
@@ -364,6 +370,7 @@ const DashboardBuilder = () => {
   const uiConfig = useUiConfig();
   const theme = useTheme();
   const { md: isNotMobile } = Grid.useBreakpoint();
+  const [mobileFiltersOpen, setMobileFiltersOpen] = useState(false);
 
   const dashboardId = useSelector<RootState, string>(
     ({ dashboardInfo }) => `${dashboardInfo.id}`,
@@ -512,6 +519,24 @@ const DashboardBuilder = () => {
     ({ dropIndicatorProps }: { dropIndicatorProps: JsonObject }) => (
       <div>
         {!hideDashboardHeader && <DashboardHeader />}
+        {/* Mobile filter button */}
+        {!isNotMobile && !editMode && nativeFiltersEnabled && (
+          <div
+            css={css`
+              padding: ${theme.sizeUnit * 2}px ${theme.sizeUnit * 4}px;
+              background: ${theme.colorBgBase};
+              border-bottom: 1px solid ${theme.colorBorderSecondary};
+            `}
+          >
+            <Button
+              buttonStyle="secondary"
+              onClick={() => setMobileFiltersOpen(true)}
+            >
+              <Icons.FilterOutlined iconSize="m" />
+              {t('Filters')}
+            </Button>
+          </div>
+        )}
         {showFilterBar &&
           filterBarOrientation === FilterBarOrientation.Horizontal && (
             <FilterBar
@@ -557,6 +582,8 @@ const DashboardBuilder = () => {
       isReport,
       topLevelTabs,
       uiConfig.hideNav,
+      isNotMobile,
+      theme,
     ],
   );
 
@@ -719,6 +746,21 @@ const DashboardBuilder = () => {
           `}
         />
       )}
+      {/* Mobile filters drawer */}
+      {!isNotMobile && nativeFiltersEnabled && (
+        <Drawer
+          title={t('Filters')}
+          placement="bottom"
+          onClose={() => setMobileFiltersOpen(false)}
+          open={mobileFiltersOpen}
+          height="70vh"
+        >
+          <FilterBar
+            orientation={FilterBarOrientation.Horizontal}
+            hidden={false}
+          />
+        </Drawer>
+      )}
     </DashboardWrapper>
   );
 };
diff --git a/superset-frontend/src/features/home/RightMenu.tsx 
b/superset-frontend/src/features/home/RightMenu.tsx
index b0dc717d67..756267e2de 100644
--- a/superset-frontend/src/features/home/RightMenu.tsx
+++ b/superset-frontend/src/features/home/RightMenu.tsx
@@ -31,6 +31,9 @@ import {
   Icons,
   Typography,
   TelemetryPixel,
+  Drawer,
+  Grid,
+  Button,
 } from '@superset-ui/core/components';
 import type { ItemType, MenuItem } from '@superset-ui/core/components/Menu';
 import { ensureAppRoot, makeUrl } from 'src/utils/pathUtils';
@@ -90,6 +93,8 @@ const StyledMenuItem = styled.div<{ disabled?: boolean }>`
   `}
 `;
 
+const { useBreakpoint } = Grid;
+
 const RightMenu = ({
   align,
   settings,
@@ -107,6 +112,8 @@ const RightMenu = ({
   }) => void;
 }) => {
   const theme = useTheme();
+  const screens = useBreakpoint();
+  const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
   const user = useSelector<any, UserWithPermissionsAndRoles>(
     state => state.user,
   );
@@ -662,48 +669,81 @@ const RightMenu = ({
             </Tag>
           );
         })()}
-      <Menu
-        css={css`
-          display: flex;
-          flex-direction: row;
-          align-items: center;
-          height: 100%;
-          border-bottom: none !important;
-
-          /* Remove the underline from menu items */
-          .ant-menu-item:after,
-          .ant-menu-submenu:after {
-            content: none !important;
-          }
-
-          .submenu-with-caret {
+      {/* Mobile: hamburger menu with drawer */}
+      {!screens.md && (
+        <>
+          <Button
+            buttonStyle="link"
+            onClick={() => setMobileMenuOpen(true)}
+            aria-label={t('Menu')}
+          >
+            <Icons.MenuOutlined iconSize="l" />
+          </Button>
+          <Drawer
+            title={t('Menu')}
+            placement="right"
+            onClose={() => setMobileMenuOpen(false)}
+            open={mobileMenuOpen}
+            width={280}
+          >
+            <Menu
+              mode="inline"
+              selectable={false}
+              onClick={info => {
+                handleMenuSelection(info);
+                setMobileMenuOpen(false);
+              }}
+              onOpenChange={onMenuOpen}
+              items={menuItems}
+            />
+          </Drawer>
+        </>
+      )}
+      {/* Desktop: horizontal menu */}
+      {screens.md && (
+        <Menu
+          css={css`
+            display: flex;
+            flex-direction: row;
+            align-items: center;
             height: 100%;
-            padding: 0;
-            .ant-menu-submenu-title {
-              align-items: center;
-              display: flex;
-              gap: ${theme.sizeUnit * 2}px;
-              flex-direction: row-reverse;
-              height: 100%;
-            }
-            &.ant-menu-submenu::after {
-              inset-inline: ${theme.sizeUnit}px;
+            border-bottom: none !important;
+
+            /* Remove the underline from menu items */
+            .ant-menu-item:after,
+            .ant-menu-submenu:after {
+              content: none !important;
             }
-            &.ant-menu-submenu:hover,
-            &.ant-menu-submenu-active {
-              .ant-menu-title-content {
-                color: ${theme.colorPrimary};
+
+            .submenu-with-caret {
+              height: 100%;
+              padding: 0;
+              .ant-menu-submenu-title {
+                align-items: center;
+                display: flex;
+                gap: ${theme.sizeUnit * 2}px;
+                flex-direction: row-reverse;
+                height: 100%;
+              }
+              &.ant-menu-submenu::after {
+                inset-inline: ${theme.sizeUnit}px;
+              }
+              &.ant-menu-submenu:hover,
+              &.ant-menu-submenu-active {
+                .ant-menu-title-content {
+                  color: ${theme.colorPrimary};
+                }
               }
             }
-          }
-        `}
-        selectable={false}
-        mode="horizontal"
-        onClick={handleMenuSelection}
-        onOpenChange={onMenuOpen}
-        disabledOverflow
-        items={menuItems}
-      />
+          `}
+          selectable={false}
+          mode="horizontal"
+          onClick={handleMenuSelection}
+          onOpenChange={onMenuOpen}
+          disabledOverflow
+          items={menuItems}
+        />
+      )}
       {navbarRight.documentation_url && (
         <>
           <StyledAnchor

Reply via email to