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;
// 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)`
${({ theme }) => css`
a {
@@ -76,10 +121,13 @@ const StyledMenuItem = styled(AntdMenu.Item)`
`;
const StyledMenu = styled(AntdMenu)`
&.ant-menu-horizontal {
background-color: inherit;
border-bottom: 1px solid transparent;
}
${({ theme }) => css`
&.ant-menu-horizontal {
background-color: inherit;
border-bottom: 1px solid transparent;
}
${sharedMenuItemStyles(theme)}
`}
`;
const StyledNav = styled(AntdMenu)`
@@ -90,60 +138,12 @@ const StyledNav = styled(AntdMenu)`
gap: 0;
border-bottom: 0;
line-height: ${theme.lineHeight};
&.ant-menu-horizontal > .ant-menu-item {
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);
}
${sharedMenuItemStyles(theme)}
`}
`;
const StyledSubMenu = styled(AntdMenu.SubMenu)`
${({ theme }) => css`
.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;
}
}
`}
/* No custom styling - use shared styles from parent Menu component */
`;
export type MenuMode = AntdMenuProps['mode'];

View File

@@ -17,14 +17,13 @@
* under the License.
*/
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 { getUrlParam } from 'src/utils/urlUtils';
import { MainNav, MenuMode } from '@superset-ui/core/components/Menu';
import { Tooltip, Grid, Row, Col, Image } from '@superset-ui/core/components';
import { GenericLink } from 'src/components';
import { NavLink, useLocation } from 'react-router-dom';
import { Icons } from '@superset-ui/core/components/Icons';
import { Typography } from '@superset-ui/core/components/Typography';
import { useUiConfig } from 'src/components/UiConfigContext';
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;
export function Menu({
@@ -179,6 +158,8 @@ export function Menu({
Dashboard = '/dashboard',
Chart = '/chart',
Datasets = '/tablemodelview',
SqlLab = '/sqllab',
SavedQueries = '/savedqueryview',
}
const defaultTabSelection: string[] = [];
@@ -196,6 +177,10 @@ export function Menu({
case path.startsWith(Paths.Datasets):
setActiveTabs(['Datasets']);
break;
case path.startsWith(Paths.SqlLab):
case path.startsWith(Paths.SavedQueries):
setActiveTabs(['SQL Lab']);
break;
default:
setActiveTabs(defaultTabSelection);
}
@@ -228,17 +213,7 @@ export function Menu({
);
}
return (
<StyledSubMenu
key={index}
title={label}
icon={
showMenu === 'inline' ? (
<></>
) : (
<Icons.CaretDownOutlined iconSize="xs" />
)
}
>
<MainNav.SubMenu key={index} title={label}>
{childs?.map((child: MenuObjectChildProps | string, index1: number) => {
if (typeof child === 'string' && child === '-' && label !== 'Data') {
return <MainNav.Divider key={`$${index1}`} />;
@@ -264,7 +239,7 @@ export function Menu({
}
return null;
})}
</StyledSubMenu>
</MainNav.SubMenu>
);
};
const renderBrand = () => {

View File

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