fix(mobile): tab bar scroll affordance, sticky behavior, spacing; drawer nav row taps

- Fade the clipped edge of the swipeable tab bar with a theme-colored
  gradient (restyling antd's ping-edge shadow elements, which are hard
  to see on dark themes) so scrollability is visible
- Let the dashboard title header scroll away on mobile instead of
  pinning underneath the higher-z sticky tab bar, where its bottom edge
  (kebab button) peeked out below the tabs
- Add padding below the tab bar so the first card doesn't butt against
  it; padding rather than margin so the gap is part of the opaque
  sticky bar when stuck
- Fix dead User Info / Logout taps in the mobile nav drawer: the reused
  desktop items navigate via anchors that only span their label text,
  but the drawer's tap target is the full row — navigate explicitly on
  row tap

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Superset Dev
2026-07-17 13:50:41 -07:00
parent 9b71958c4a
commit fa4f62cdcb
3 changed files with 77 additions and 12 deletions

View File

@@ -59,7 +59,7 @@ import {
} from 'src/dashboard/util/constants';
import FilterBar from 'src/dashboard/components/nativeFilters/FilterBar';
import { useUiConfig } from 'src/components/UiConfigContext';
import { useIsMobile } from 'src/hooks/useIsMobile';
import { isMobileConsumptionEnabled, useIsMobile } from 'src/hooks/useIsMobile';
import ResizableSidebar from 'src/components/ResizableSidebar';
import {
BUILDER_SIDEPANEL_WIDTH,
@@ -102,6 +102,19 @@ const StyledHeader = styled.div<{ filterBarWidth: number }>`
z-index: 99;
max-width: calc(100vw - ${filterBarWidth}px);
/* Mobile consumption mode: let the dashboard title scroll away and keep
only the tab bar sticky. A pinned title would sit underneath the
higher-z sticky tabs, leaving its bottom edge (kebab button) peeking
out below the tab bar. */
${
isMobileConsumptionEnabled() &&
css`
@media (max-width: ${theme.screenSMMax}px) {
position: relative;
}
`
}
.empty-droptarget {
min-height: ${theme.sizeUnit * 4}px;
}
@@ -143,8 +156,9 @@ const DashboardContentWrapper = styled.div`
& .dashboard-component-tabs {
box-shadow: 0 ${theme.sizeUnit}px ${theme.sizeUnit}px 0
${addAlpha(theme.colorBorderSecondary, 0.1)};
padding-left: ${theme.sizeUnit *
2}px; /* note this is added to tab-level padding, to match header */
padding-left: ${
theme.sizeUnit * 2
}px; /* note this is added to tab-level padding, to match header */
}
.dropdown-toggle.btn.btn-primary .caret {
@@ -297,12 +311,14 @@ const StyledDashboardContent = styled.div<{
margin: ${theme.sizeUnit * 4}px;
margin-left: ${marginLeft}px;
${editMode &&
`
${
editMode &&
`
max-width: calc(100% - ${
BUILDER_SIDEPANEL_WIDTH + theme.sizeUnit * 16
}px);
`}
`
}
/* this is the ParentSize wrapper */
& > div:first-of-type {
@@ -339,8 +355,10 @@ const StyledDashboardContent = styled.div<{
}
&.fade-out {
box-shadow: ${theme.dashboardTileBoxShadow ??
`0 0 0 1px ${addAlpha(theme.colorBorder, 0.5)}`};
box-shadow: ${
theme.dashboardTileBoxShadow ??
`0 0 0 1px ${addAlpha(theme.colorBorder, 0.5)}`
};
}
& .missing-chart-container {

View File

@@ -93,6 +93,36 @@ const StyledTabsContainer = styled.div<{ isDragging?: boolean }>`
top: 0;
z-index: 100;
background-color: ${theme.colorBgContainer};
/* breathing room between the tab bar and the first card; padding
(not margin) so the gap is part of the opaque sticky bar */
padding-bottom: ${theme.sizeUnit * 2}px;
}
/* Scrollability affordance: fade the clipped edge with a
theme-colored gradient. antd toggles the ping classes when tabs
overflow on that side; restyle its shadow elements as gradients,
which read much better than the default shadows on dark themes. */
.ant-tabs-nav-wrap:before,
.ant-tabs-nav-wrap:after {
width: ${theme.sizeUnit * 10}px;
box-shadow: none !important;
pointer-events: none;
}
.ant-tabs-nav-wrap-ping-right:after {
background: linear-gradient(
to right,
transparent,
${theme.colorBgContainer}
);
opacity: 1;
}
.ant-tabs-nav-wrap-ping-left:before {
background: linear-gradient(
to left,
transparent,
${theme.colorBgContainer}
);
opacity: 1;
}
/* Swipeable tab bar instead of the overflow dropdown: the "more"

View File

@@ -107,10 +107,12 @@ const StyledMenuItem = styled.div<{ disabled?: boolean }>`
color: ${!disabled && theme.colorPrimary};
cursor: ${!disabled ? 'pointer' : 'not-allowed'};
}
${disabled &&
css`
color: ${theme.colorTextDisabled};
`}
${
disabled &&
css`
color: ${theme.colorTextDisabled};
`
}
`}
`;
@@ -794,6 +796,21 @@ const RightMenu = ({
selectable={false}
onClick={info => {
handleMenuSelection(info);
// The reused desktop items navigate via anchors that only
// span their label text, but the drawer's tap target is the
// full menu row — navigate explicitly so row taps work.
if (info.key === 'info' && navbarRight.user_info_url) {
window.location.assign(
ensureAppRoot(navbarRight.user_info_url),
);
return;
}
if (info.key === 'logout' && navbarRight.user_logout_url) {
window.location.assign(
ensureAppRoot(navbarRight.user_logout_url),
);
return;
}
setMobileMenuOpen(false);
}}
items={mobileMenuItems}