Compare commits

...

3 Commits

Author SHA1 Message Date
Maxime Beauchemin
3da6b37867 active SQL 2025-08-18 12:08:33 -07:00
Maxime Beauchemin
cd18390dd0 fix: remove menu carets and ensure consistent underline animations
- Remove all caret icons from menu dropdowns for cleaner vanilla Ant Design approach
- Create shared menu item styling in superset-ui-core for consistent underline animations
- Fix vertical centering of submenu title content
- Hide unused ant-menu-submenu-arrow elements
- Apply consistent height and positioning to both regular items and dropdown items
- Remove custom components in favor of shared core styling
- Both left (MainNav) and right (Menu) menus now have identical underline behavior

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-18 11:43:22 -07:00
Maxime Beauchemin
36880c088c fix: standardize menu caret positioning with shared styled component
- Create shared StyledDropdownSubMenu component with consistent caret styling
- Update left menu (Menu.tsx) to use shared component instead of local implementation
- Refactor right menu (RightMenu.tsx) from MenuItem API to SubMenu components for consistency
- Fix caret positioning so all dropdown menus show caret on right side of label
- Remove code duplication between left and right menu implementations

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-16 15:43:51 -07:00
3 changed files with 63 additions and 90 deletions

View File

@@ -41,6 +41,51 @@ export type AntdMenuItemType = ReactElement & {
export type MenuItemChildType = AntdMenuItemType; export type MenuItemChildType = AntdMenuItemType;
// Shared menu item styling for consistent underline animation
const sharedMenuItemStyles = (theme: any) => css`
&.ant-menu-horizontal > .ant-menu-item,
&.ant-menu-horizontal > .ant-menu-submenu {
height: 100%;
display: flex;
align-items: center;
margin: 0;
padding: ${theme.sizeUnit * 2}px ${theme.sizeUnit * 4}px;
::after {
content: '';
position: absolute;
width: 98%;
height: 2px;
background-color: ${theme.colorPrimaryBorderHover};
bottom: ${theme.sizeUnit / 4}px;
left: 0;
transform: scale(0);
transition: 0.2s all ease-out;
}
:hover::after {
transform: scale(1);
}
}
&.ant-menu-horizontal > .ant-menu-item-selected::after,
&.ant-menu-horizontal > .ant-menu-submenu-selected::after {
transform: scale(1);
}
&.ant-menu-horizontal > .ant-menu-submenu .ant-menu-submenu-title {
height: 100%;
display: flex;
align-items: center;
.ant-menu-title-content {
display: flex;
align-items: center;
}
/* Hide the empty arrow element */
.ant-menu-submenu-arrow {
display: none;
}
}
`;
const StyledMenuItem = styled(AntdMenu.Item)` const StyledMenuItem = styled(AntdMenu.Item)`
${({ theme }) => css` ${({ theme }) => css`
a { a {
@@ -76,10 +121,13 @@ const StyledMenuItem = styled(AntdMenu.Item)`
`; `;
const StyledMenu = styled(AntdMenu)` const StyledMenu = styled(AntdMenu)`
&.ant-menu-horizontal { ${({ theme }) => css`
background-color: inherit; &.ant-menu-horizontal {
border-bottom: 1px solid transparent; background-color: inherit;
} border-bottom: 1px solid transparent;
}
${sharedMenuItemStyles(theme)}
`}
`; `;
const StyledNav = styled(AntdMenu)` const StyledNav = styled(AntdMenu)`
@@ -90,60 +138,12 @@ const StyledNav = styled(AntdMenu)`
gap: 0; gap: 0;
border-bottom: 0; border-bottom: 0;
line-height: ${theme.lineHeight}; line-height: ${theme.lineHeight};
&.ant-menu-horizontal > .ant-menu-item { ${sharedMenuItemStyles(theme)}
height: 100%;
display: flex;
align-items: center;
margin: 0;
padding: ${theme.sizeUnit * 2}px ${theme.sizeUnit * 4}px;
::after {
content: '';
position: absolute;
width: 98%;
height: 2px;
background-color: ${theme.colorPrimaryBorderHover};
bottom: ${theme.sizeUnit / 4}px;
left: 0;
transform: scale(0);
transition: 0.2s all ease-out;
}
:hover::after {
transform: scale(1);
}
}
&.ant-menu-horizontal > .ant-menu-item-selected::after {
transform: scale(1);
}
`} `}
`; `;
const StyledSubMenu = styled(AntdMenu.SubMenu)` const StyledSubMenu = styled(AntdMenu.SubMenu)`
${({ theme }) => css` /* No custom styling - use shared styles from parent Menu component */
.ant-menu-submenu-open,
.ant-menu-submenu-active {
.ant-menu-submenu-title {
&:after {
opacity: 1;
width: calc(100% - 1);
}
}
}
.ant-menu-submenu-title {
display: flex;
flex-direction: row-reverse;
&:after {
content: '';
position: absolute;
bottom: -3px;
left: 50%;
width: 0;
height: 3px;
opacity: 0;
transform: translateX(-50%);
transition: all ${theme.transitionTiming}s;
}
}
`}
`; `;
export type MenuMode = AntdMenuProps['mode']; export type MenuMode = AntdMenuProps['mode'];

View File

@@ -17,14 +17,13 @@
* under the License. * under the License.
*/ */
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { styled, css, useTheme } from '@superset-ui/core'; import { styled, useTheme } from '@superset-ui/core';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { getUrlParam } from 'src/utils/urlUtils'; import { getUrlParam } from 'src/utils/urlUtils';
import { MainNav, MenuMode } from '@superset-ui/core/components/Menu'; import { MainNav, MenuMode } from '@superset-ui/core/components/Menu';
import { Tooltip, Grid, Row, Col, Image } from '@superset-ui/core/components'; import { Tooltip, Grid, Row, Col, Image } from '@superset-ui/core/components';
import { GenericLink } from 'src/components'; import { GenericLink } from 'src/components';
import { NavLink, useLocation } from 'react-router-dom'; import { NavLink, useLocation } from 'react-router-dom';
import { Icons } from '@superset-ui/core/components/Icons';
import { Typography } from '@superset-ui/core/components/Typography'; import { Typography } from '@superset-ui/core/components/Typography';
import { useUiConfig } from 'src/components/UiConfigContext'; import { useUiConfig } from 'src/components/UiConfigContext';
import { URL_PARAMS } from 'src/constants'; import { URL_PARAMS } from 'src/constants';
@@ -125,26 +124,6 @@ const StyledHeader = styled.header`
} }
`} `}
`; `;
const { SubMenu } = MainNav;
const StyledSubMenu = styled(SubMenu)`
${({ theme }) => css`
[data-icon="caret-down"] {
color: ${theme.colors.grayscale.base};
font-size: ${theme.fontSizeXS}px;
margin-left: ${theme.sizeUnit}px;
}
&.ant-menu-submenu {
padding: ${theme.sizeUnit * 2}px ${theme.sizeUnit * 4}px;
display: flex;
align-items: center;
height: 100%; &.ant-menu-submenu-active {
.ant-menu-title-content {
color: ${theme.colorPrimary};
}
}
`}
`;
const { useBreakpoint } = Grid; const { useBreakpoint } = Grid;
export function Menu({ export function Menu({
@@ -179,6 +158,8 @@ export function Menu({
Dashboard = '/dashboard', Dashboard = '/dashboard',
Chart = '/chart', Chart = '/chart',
Datasets = '/tablemodelview', Datasets = '/tablemodelview',
SqlLab = '/sqllab',
SavedQueries = '/savedqueryview',
} }
const defaultTabSelection: string[] = []; const defaultTabSelection: string[] = [];
@@ -196,6 +177,10 @@ export function Menu({
case path.startsWith(Paths.Datasets): case path.startsWith(Paths.Datasets):
setActiveTabs(['Datasets']); setActiveTabs(['Datasets']);
break; break;
case path.startsWith(Paths.SqlLab):
case path.startsWith(Paths.SavedQueries):
setActiveTabs(['SQL Lab']);
break;
default: default:
setActiveTabs(defaultTabSelection); setActiveTabs(defaultTabSelection);
} }
@@ -228,17 +213,7 @@ export function Menu({
); );
} }
return ( return (
<StyledSubMenu <MainNav.SubMenu key={index} title={label}>
key={index}
title={label}
icon={
showMenu === 'inline' ? (
<></>
) : (
<Icons.CaretDownOutlined iconSize="xs" />
)
}
>
{childs?.map((child: MenuObjectChildProps | string, index1: number) => { {childs?.map((child: MenuObjectChildProps | string, index1: number) => {
if (typeof child === 'string' && child === '-' && label !== 'Data') { if (typeof child === 'string' && child === '-' && label !== 'Data') {
return <MainNav.Divider key={`$${index1}`} />; return <MainNav.Divider key={`$${index1}`} />;
@@ -264,7 +239,7 @@ export function Menu({
} }
return null; return null;
})} })}
</StyledSubMenu> </MainNav.SubMenu>
); );
}; };
const renderBrand = () => { const renderBrand = () => {

View File

@@ -570,7 +570,6 @@ const RightMenu = ({
data-test="new-dropdown-icon" data-test="new-dropdown-icon"
/> />
), ),
icon: <Icons.CaretDownOutlined iconSize="xs" />,
children: buildNewDropdownItems(), children: buildNewDropdownItems(),
...{ 'data-test': 'new-dropdown' }, ...{ 'data-test': 'new-dropdown' },
}); });
@@ -587,7 +586,6 @@ const RightMenu = ({
items.push({ items.push({
key: 'settings', key: 'settings',
label: t('Settings'), label: t('Settings'),
icon: <Icons.CaretDownOutlined iconSize="xs" />,
children: buildSettingsMenuItems(), children: buildSettingsMenuItems(),
}); });