{
expect(css).toContain('body[data-ui-version="v2"] .gn-v2-query-toolbar-action-pair {');
});
+ it('uses the table context-menu density across v2 action menus', () => {
+ const css = readV2ThemeCss();
+ const baseTokensCss = css.slice(
+ css.indexOf('body[data-ui-version="v2"] {'),
+ css.indexOf('body[data-ui-version="v2"][data-platform="darwin"] {'),
+ );
+ const dropdownItemCss = css.slice(
+ css.indexOf('/* Compact action menus share the table context-menu geometry. */'),
+ css.indexOf('/* Icon + label share one baseline/vertical center'),
+ );
+ const titlebarMenuCss = css.slice(
+ css.indexOf('/* Title-bar more menu: leaf rows and submenu rows share the same hover fill + alignment */'),
+ css.indexOf('body[data-ui-version="v2"] .gn-v2-titlebar-quick-dropdown .ant-dropdown-menu-item:hover'),
+ );
+ const tabMenuCss = css.slice(
+ css.indexOf('body[data-ui-version="v2"] .gn-v2-tab-context-menu-popup .ant-dropdown-menu {'),
+ css.indexOf('/* ─── V2 DataGrid: toolbar, smart filters, table, statusbar ─ */'),
+ );
+ const tableMenuItemCss = css.slice(
+ css.indexOf('body[data-ui-version="v2"] .gn-v2-context-menu-item {'),
+ css.indexOf('body[data-ui-version="v2"] .gn-v2-context-menu-item:hover {'),
+ );
+ const monacoMenuCss = css.slice(
+ css.indexOf('/* Monaco context menu follows the same compact action-menu surface. */'),
+ css.indexOf('/* Nested dropdown menus: collapse double border, no glow/halo */'),
+ );
+
+ expect(baseTokensCss).toContain('--gn-v2-menu-row-height: 28px;');
+ expect(baseTokensCss).toContain('--gn-v2-menu-item-radius: 5px;');
+ expect(dropdownItemCss).toContain('height: var(--gn-v2-menu-row-height) !important;');
+ expect(dropdownItemCss).toContain('min-height: var(--gn-v2-menu-row-height) !important;');
+ expect(dropdownItemCss).toContain('box-sizing: border-box !important;');
+ expect(dropdownItemCss).toContain('padding: 4px 8px !important;');
+ expect(titlebarMenuCss).toContain('min-height: var(--gn-v2-menu-row-height) !important;');
+ expect(tabMenuCss).not.toContain('min-height: 34px');
+ expect(tabMenuCss).not.toContain('.ant-dropdown-menu-item:first-child');
+ expect(tableMenuItemCss).toContain('height: var(--gn-v2-menu-row-height);');
+ expect(monacoMenuCss).toContain('min-height: var(--gn-v2-menu-row-height) !important;');
+ expect(monacoMenuCss).toContain('background: var(--gn-bg-panel) !important;');
+ });
+
it('shares the active theme surface across the SQL toolbar and Monaco editor', () => {
const css = readV2ThemeCss();
const defaultMonacoCss = css.slice(
@@ -268,4 +309,20 @@ describe('QueryEditorToolbar layout', () => {
expect(css).toContain('text-overflow: ellipsis;');
expect(css).toContain('white-space: nowrap;');
});
+
+ it('uses the table context-menu visual grammar for every v2 action popup', () => {
+ const toolbarSource = readFileSync(new URL('./QueryEditorToolbar.tsx', import.meta.url), 'utf8');
+ const queryEditorSource = readFileSync(new URL('./QueryEditor.tsx', import.meta.url), 'utf8');
+ const sharedPopupSource = readFileSync(new URL('./common/V2ActionMenuPopup.tsx', import.meta.url), 'utf8');
+ const css = readV2ThemeCss();
+
+ expect(toolbarSource).toContain('renderV2ActionMenuPopup');
+ expect(queryEditorSource).toContain('decorateV2MonacoContextMenu');
+ expect(sharedPopupSource).toContain('gn-v2-context-menu-header gn-v2-action-menu-header');
+ expect(sharedPopupSource).toContain('gn-v2-context-menu-engine-pill');
+ expect(css).toContain('.gn-v2-action-menu-surface {');
+ expect(css).toContain('.gn-v2-action-menu-body .ant-dropdown-menu-item-group-title {');
+ expect(css).toContain('.gn-v2-action-menu-body .ant-dropdown-menu-item:not(:has(.ant-dropdown-menu-item-icon))::before');
+ expect(css).toContain('.monaco-menu > .gn-v2-monaco-context-menu-header {');
+ });
});
diff --git a/frontend/src/components/QueryEditorToolbar.tsx b/frontend/src/components/QueryEditorToolbar.tsx
index 8e300312..0e16170e 100644
--- a/frontend/src/components/QueryEditorToolbar.tsx
+++ b/frontend/src/components/QueryEditorToolbar.tsx
@@ -26,6 +26,7 @@ import {
import QueryEditorTransactionSettings, {
type SqlEditorCommitMode,
} from "./QueryEditorTransactionSettings";
+import { renderV2ActionMenuPopup } from './common/V2ActionMenuPopup';
export type QueryEditorMode = "sql" | "elasticsearch";
@@ -304,6 +305,11 @@ const QueryEditorToolbar: React.FC
= ({
? t("query_editor.elasticsearch.action.ai")
: `AI · ${t("query_editor.action.more")}`;
const formatSettingsTitle = `${t("query_editor.action.format_sql")} · ${t("settings.title")}`;
+ const activeConnectionLabel = queryCapableConnections.find(
+ (connection) => connection.id === currentConnectionId,
+ )?.name;
+ const menuContextMeta = [activeConnectionLabel, currentDb].filter(Boolean).join(' · ')
+ || t('tab_manager.kind_badge.query');
const aiMenuItems: MenuProps["items"] = isElasticsearchMode
? [
{
@@ -350,16 +356,20 @@ const QueryEditorToolbar: React.FC = ({
const moreMenuItems: MenuProps["items"] = isV2Ui
? [
...baseMoreMenuItems,
- ...(baseMoreMenuItems.length > 0 ? [{ type: "divider" as const }] : []),
{
- key: "toggle-result-panel",
- label: toggleResultPanelTitle,
- icon: isResultPanelVisible ? (
-
- ) : (
-
- ),
- onClick: onToggleResultPanelVisibility,
+ type: 'group',
+ key: 'result-visibility',
+ label: t('query_editor.action.results'),
+ children: [{
+ key: "toggle-result-panel",
+ label: toggleResultPanelTitle,
+ icon: isResultPanelVisible ? (
+
+ ) : (
+
+ ),
+ onClick: onToggleResultPanelVisibility,
+ }],
},
]
: baseMoreMenuItems;
@@ -437,11 +447,17 @@ const QueryEditorToolbar: React.FC = ({
open={isV2Ui && openToolbarMenu === "templates" ? false : undefined}
>
- renderV2ActionMenuPopup(menu, isV2Ui, {
+ title: t('query_editor.elasticsearch.action.templates'),
+ meta: menuContextMeta,
+ icon: ,
+ })}
+ open={isV2Ui ? openToolbarMenu === "templates" : undefined}
onOpenChange={isV2Ui ? (open) => updateToolbarMenuOpen("templates", open) : undefined}
>
}
onClick={async () => {
try {
diff --git a/frontend/src/components/TabManager.tsx b/frontend/src/components/TabManager.tsx
index 0e7e4921..fcb6dc54 100644
--- a/frontend/src/components/TabManager.tsx
+++ b/frontend/src/components/TabManager.tsx
@@ -1,7 +1,7 @@
import Modal from './common/ResizableDraggableModal';
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import { Button, Dropdown, message, Tabs, Tooltip } from 'antd';
-import { CloseOutlined, ConsoleSqlOutlined, DatabaseOutlined, EditOutlined, FileTextOutlined, FolderOpenOutlined, HistoryOutlined, PlusOutlined, PushpinOutlined, RightOutlined, RobotOutlined, SearchOutlined, SettingOutlined } from '@ant-design/icons';
+import { ArrowLeftOutlined, ArrowRightOutlined, CloseCircleOutlined, CloseOutlined, ConsoleSqlOutlined, DatabaseOutlined, EditOutlined, ExportOutlined, FileTextOutlined, FolderOpenOutlined, HistoryOutlined, PlusOutlined, PushpinOutlined, RightOutlined, RobotOutlined, SearchOutlined, SettingOutlined } from '@ant-design/icons';
import type { MenuProps, TabsProps } from 'antd';
import { DndContext, PointerSensor, closestCenter, useSensor, useSensors } from '@dnd-kit/core';
import type { DragEndEvent, DragMoveEvent, DragStartEvent } from '@dnd-kit/core';
@@ -57,6 +57,7 @@ import { QUERY_TAB_RENAME_REQUEST_EVENT } from '../utils/queryTabTitle';
import { getDbIcon } from './DatabaseIcons';
import { resolveConnectionAccentColor, resolveConnectionIconType } from '../utils/connectionVisual';
import { dispatchSidebarLocateConnection } from '../utils/sidebarLocate';
+import { renderV2ActionMenuPopup } from './common/V2ActionMenuPopup';
const getTabKindLabel = (tab: TabData): string => {
if (tab.type === 'query') return t('tab_manager.kind_badge.query');
@@ -632,6 +633,12 @@ const SortableTabLabel: React.FC = ({
trigger={['contextMenu']}
onOpenChange={handleTabMenuOpenChange}
rootClassName={isV2Ui ? 'gn-v2-tab-context-menu-popup' : undefined}
+ popupRender={(menu) => renderV2ActionMenuPopup(menu, Boolean(isV2Ui), {
+ title: displayTitle,
+ meta: displayModel.secondaryText || getTabKindLabel(tab),
+ icon: tab.type === 'query' ? : ,
+ badge: getTabKindLabel(tab),
+ })}
>
{wrappedLabel}
@@ -1337,57 +1344,74 @@ const TabManager: React.FC = React.memo(({ onF
const renameQueryMenuState = resolveQueryTabRenameMenuState(tab);
const menuItems: MenuProps['items'] = [
- ...(renameQueryMenuState.visible ? [{
- key: 'rename-query',
- icon: ,
- label: t('query_editor.action.rename_query'),
- disabled: renameQueryMenuState.disabled,
- onClick: () => {
- setActiveTab(tab.id);
- window.setTimeout(() => {
- window.dispatchEvent(new CustomEvent(QUERY_TAB_RENAME_REQUEST_EVENT, {
- detail: { tabId: tab.id },
- }));
- }, 0);
- },
- }] : []),
{
- key: 'tab-display-settings',
- icon: ,
- label: t('tab_manager.menu.tab_display_settings'),
- onClick: openTabDisplaySettings,
+ type: 'group',
+ key: 'tab-actions',
+ label: t('tab_manager.kind_badge.fallback'),
+ children: [
+ ...(renameQueryMenuState.visible ? [{
+ key: 'rename-query',
+ icon: ,
+ label: t('query_editor.action.rename_query'),
+ disabled: renameQueryMenuState.disabled,
+ onClick: () => {
+ setActiveTab(tab.id);
+ window.setTimeout(() => {
+ window.dispatchEvent(new CustomEvent(QUERY_TAB_RENAME_REQUEST_EVENT, {
+ detail: { tabId: tab.id },
+ }));
+ }, 0);
+ },
+ }] : []),
+ {
+ key: 'tab-display-settings',
+ icon: ,
+ label: t('tab_manager.menu.tab_display_settings'),
+ onClick: openTabDisplaySettings,
+ },
+ {
+ key: 'open-in-window',
+ icon: ,
+ label: t('tab_manager.menu.open_in_window'),
+ disabled: isBackgroundTaskWorkbenchTab(tab),
+ onClick: () => detachTabToWindow(tab.id),
+ },
+ ],
},
{
- key: 'open-in-window',
- label: t('tab_manager.menu.open_in_window'),
- disabled: isBackgroundTaskWorkbenchTab(tab),
- onClick: () => detachTabToWindow(tab.id),
- },
- { type: 'divider' },
- {
- key: 'close-other',
- label: t('tab_manager.menu.close_other'),
- disabled: tabs.length <= 1,
- onClick: () => closeTabsWithSQLFilePrompt(getCloseOtherTabIds(tabs, tab.id), () => closeOtherTabs(tab.id)),
- },
- {
- key: 'close-left',
- label: t('tab_manager.menu.close_left'),
- disabled: index === 0,
- onClick: () => closeTabsWithSQLFilePrompt(getCloseTabsToLeftIds(dockedTabs, tab.id), () => closeTabsToLeft(tab.id)),
- },
- {
- key: 'close-right',
- label: t('tab_manager.menu.close_right'),
- disabled: index === dockedTabs.length - 1,
- onClick: () => closeTabsWithSQLFilePrompt(getCloseTabsToRightIds(dockedTabs, tab.id), () => closeTabsToRight(tab.id)),
- },
- { type: 'divider' },
- {
- key: 'close-all',
- label: t('tab_manager.menu.close_all'),
- disabled: tabs.length === 0,
- onClick: () => closeTabsWithSQLFilePrompt(tabs.map((item) => item.id), () => closeAllTabs()),
+ type: 'group',
+ key: 'close-actions',
+ label: t('common.close'),
+ children: [
+ {
+ key: 'close-other',
+ icon: ,
+ label: t('tab_manager.menu.close_other'),
+ disabled: tabs.length <= 1,
+ onClick: () => closeTabsWithSQLFilePrompt(getCloseOtherTabIds(tabs, tab.id), () => closeOtherTabs(tab.id)),
+ },
+ {
+ key: 'close-left',
+ icon: ,
+ label: t('tab_manager.menu.close_left'),
+ disabled: index === 0,
+ onClick: () => closeTabsWithSQLFilePrompt(getCloseTabsToLeftIds(dockedTabs, tab.id), () => closeTabsToLeft(tab.id)),
+ },
+ {
+ key: 'close-right',
+ icon: ,
+ label: t('tab_manager.menu.close_right'),
+ disabled: index === dockedTabs.length - 1,
+ onClick: () => closeTabsWithSQLFilePrompt(getCloseTabsToRightIds(dockedTabs, tab.id), () => closeTabsToRight(tab.id)),
+ },
+ {
+ key: 'close-all',
+ icon: ,
+ label: t('tab_manager.menu.close_all'),
+ disabled: tabs.length === 0,
+ onClick: () => closeTabsWithSQLFilePrompt(tabs.map((item) => item.id), () => closeAllTabs()),
+ },
+ ],
},
];
diff --git a/frontend/src/components/TitleBarQuickActions.tsx b/frontend/src/components/TitleBarQuickActions.tsx
index 486468e3..972561c9 100644
--- a/frontend/src/components/TitleBarQuickActions.tsx
+++ b/frontend/src/components/TitleBarQuickActions.tsx
@@ -2,6 +2,7 @@ import React, { useState } from 'react';
import { Dropdown, Tooltip } from 'antd';
import type { MenuProps } from 'antd';
import { MoreOutlined } from '@ant-design/icons';
+import { renderV2ActionMenuPopup } from './common/V2ActionMenuPopup';
export interface TitleBarQuickAction {
key: string;
@@ -65,7 +66,12 @@ const TitleBarQuickActions: React.FC = ({ label, more
menu={{ items: buildMenuItems(action.menu), className: 'gn-v2-titlebar-quick-menu' }}
trigger={['click']}
placement="bottomLeft"
- rootClassName="gn-v2-titlebar-quick-dropdown"
+ rootClassName="gn-v2-titlebar-quick-dropdown gn-v2-action-menu-popup-host"
+ popupRender={(menu) => renderV2ActionMenuPopup(menu, true, {
+ title: action.label,
+ meta: label,
+ icon: action.icon,
+ })}
open={openMenuKey === action.key}
onOpenChange={(open) => handleMenuOpenChange(action.key, open)}
>
@@ -108,7 +114,12 @@ const TitleBarQuickActions: React.FC = ({ label, more
menu={dropdownMenuProps}
trigger={['click']}
placement="bottomLeft"
- rootClassName="gn-v2-titlebar-quick-dropdown"
+ rootClassName="gn-v2-titlebar-quick-dropdown gn-v2-action-menu-popup-host"
+ popupRender={(menu) => renderV2ActionMenuPopup(menu, true, {
+ title: moreLabel,
+ meta: label,
+ icon: ,
+ })}
open={openMenuKey === '__more__'}
onOpenChange={(open) => handleMenuOpenChange('__more__', open)}
>
diff --git a/frontend/src/components/common/V2ActionMenuPopup.tsx b/frontend/src/components/common/V2ActionMenuPopup.tsx
new file mode 100644
index 00000000..09308cac
--- /dev/null
+++ b/frontend/src/components/common/V2ActionMenuPopup.tsx
@@ -0,0 +1,173 @@
+import React from 'react';
+
+export interface V2ActionMenuPopupProps {
+ title: React.ReactNode;
+ meta?: React.ReactNode;
+ icon?: React.ReactNode;
+ badge?: React.ReactNode;
+ children: React.ReactNode;
+}
+
+const V2ActionMenuPopup: React.FC = ({
+ title,
+ meta,
+ icon,
+ badge,
+ children,
+}) => (
+
+
+
+ {icon}
+
+
+ {title}
+ {meta ? {meta} : null}
+
+ {badge ? {badge} : null}
+
+
{children}
+
+);
+
+export const renderV2ActionMenuPopup = (
+ menu: React.ReactNode,
+ enabled: boolean,
+ props: Omit,
+): React.ReactNode => (
+ enabled ? {menu} : menu
+);
+
+const MONACO_CONTEXT_MENU_STYLE_ID = 'gn-v2-monaco-context-menu-styles';
+const MONACO_CONTEXT_MENU_STYLES = `
+.monaco-menu {
+ min-width: 264px !important;
+ overflow: hidden !important;
+ padding: 4px !important;
+ border: 0.5px solid var(--gn-br-2) !important;
+ border-radius: var(--gn-v2-menu-surface-radius, 10px) !important;
+ background: var(--gn-bg-panel) !important;
+ color: var(--gn-fg-1) !important;
+ box-shadow: var(--gn-shadow-lg) !important;
+}
+.monaco-menu > .gn-v2-monaco-context-menu-header {
+ display: grid;
+ grid-template-columns: 16px minmax(0, 1fr);
+ column-gap: 6px;
+ row-gap: 3px;
+ align-items: center;
+ margin: -4px -4px 4px;
+ padding: 10px 12px;
+ border-bottom: 0.5px solid var(--gn-br-1);
+ border-radius: 9px 9px 0 0;
+ background: var(--gn-bg-panel-2);
+ font-family: var(--gn-font-sans);
+}
+.gn-v2-monaco-context-menu-header .gn-v2-action-menu-header-icon {
+ width: 16px;
+ color: var(--gn-info);
+ font-family: var(--gn-font-mono);
+ font-size: 8px;
+ font-weight: 700;
+ letter-spacing: -0.04em;
+}
+.gn-v2-monaco-context-menu-header .gn-v2-context-menu-heading { display: contents; }
+.gn-v2-monaco-context-menu-header strong {
+ grid-column: 2;
+ min-width: 0;
+ overflow: hidden;
+ color: var(--gn-fg-1);
+ font-family: var(--gn-font-mono);
+ font-size: 12.5px;
+ font-weight: 600;
+ line-height: 1.25;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+.gn-v2-monaco-context-menu-header small {
+ grid-column: 1 / -1;
+ overflow: hidden;
+ color: var(--gn-fg-4);
+ font-family: var(--gn-font-mono);
+ font-size: 10.5px;
+ line-height: 1.25;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+.monaco-menu .monaco-action-bar.vertical .action-menu-item,
+.monaco-menu .monaco-action-bar.vertical .action-label:not(.separator) {
+ min-height: var(--gn-v2-menu-row-height, 28px) !important;
+ border-radius: var(--gn-v2-menu-item-radius, 5px) !important;
+ font-size: 12.5px !important;
+ line-height: var(--gn-v2-menu-row-height, 28px) !important;
+}
+.monaco-menu .monaco-action-bar.vertical .action-label:not(.separator) {
+ padding: 0 8px !important;
+}
+.monaco-menu .monaco-action-bar.vertical .action-menu-item:focus {
+ background: var(--gn-bg-active) !important;
+ color: var(--gn-fg-1) !important;
+ outline: none !important;
+}
+.monaco-menu .monaco-action-bar .action-item .action-label.separator {
+ width: auto !important;
+ height: 1px !important;
+ min-height: 1px !important;
+ margin: 4px !important;
+ padding: 0 !important;
+ border: 0 !important;
+ background: var(--gn-br-1) !important;
+}
+`;
+
+export const decorateV2MonacoContextMenu = (title: string, meta?: string): void => {
+ const roots: Array = [document];
+ document.querySelectorAll('*').forEach((element) => {
+ if (element.shadowRoot) roots.push(element.shadowRoot);
+ });
+
+ const menus = roots.flatMap((root) => {
+ if (root instanceof ShadowRoot && !root.getElementById(MONACO_CONTEXT_MENU_STYLE_ID)) {
+ const style = document.createElement('style');
+ style.id = MONACO_CONTEXT_MENU_STYLE_ID;
+ style.textContent = MONACO_CONTEXT_MENU_STYLES;
+ root.append(style);
+ }
+ return Array.from(root.querySelectorAll('.monaco-menu'));
+ }).filter((menu) => menu.getClientRects().length > 0);
+ const menu = menus[menus.length - 1];
+ if (!menu) return;
+
+ const existing = menu.querySelector(':scope > .gn-v2-monaco-context-menu-header');
+ if (existing) {
+ const titleElement = existing.querySelector('.gn-v2-context-menu-heading strong');
+ if (titleElement) titleElement.textContent = title;
+ const metaElement = existing.querySelector('.gn-v2-context-menu-heading small');
+ if (metaElement) metaElement.textContent = meta || '';
+ return;
+ }
+
+ const header = document.createElement('div');
+ header.className = 'gn-v2-context-menu-header gn-v2-action-menu-header gn-v2-monaco-context-menu-header';
+
+ const icon = document.createElement('span');
+ icon.className = 'gn-v2-context-menu-table-icon gn-v2-action-menu-header-icon gn-v2-monaco-context-menu-icon';
+ icon.setAttribute('aria-hidden', 'true');
+ icon.textContent = 'SQL';
+
+ const heading = document.createElement('span');
+ heading.className = 'gn-v2-context-menu-heading';
+ const strong = document.createElement('strong');
+ strong.textContent = title;
+ heading.appendChild(strong);
+ if (meta) {
+ const small = document.createElement('small');
+ small.textContent = meta;
+ heading.appendChild(small);
+ }
+
+ header.append(icon, heading);
+ menu.prepend(header);
+};
+
+export default V2ActionMenuPopup;
diff --git a/frontend/src/styles/v2-theme-workbench.css b/frontend/src/styles/v2-theme-workbench.css
index b5388400..9c65aed6 100644
--- a/frontend/src/styles/v2-theme-workbench.css
+++ b/frontend/src/styles/v2-theme-workbench.css
@@ -989,7 +989,7 @@ body[data-ui-version="v2"] .gn-v2-redis-data-section .ant-table-wrapper {
body[data-ui-version="v2"] .gn-v2-redis-context-menu {
width: 208px !important;
min-width: 188px !important;
- padding: 6px !important;
+ padding: var(--gn-v2-menu-surface-padding) !important;
}
/* ─── V2 Nacos workbench (flat split + aligned headers, match Redis) ─ */
diff --git a/frontend/src/v2-theme.css b/frontend/src/v2-theme.css
index 273551c5..6b80c5ca 100644
--- a/frontend/src/v2-theme.css
+++ b/frontend/src/v2-theme.css
@@ -130,6 +130,10 @@ body[data-ui-version="v2"]:not([data-custom-theme]) {
/* ─── Base typography & body ──────────────────────────── */
body[data-ui-version="v2"] {
+ --gn-v2-menu-row-height: 28px;
+ --gn-v2-menu-item-radius: 5px;
+ --gn-v2-menu-surface-radius: 10px;
+ --gn-v2-menu-surface-padding: 4px;
font-family: var(--gn-font-sans) !important;
font-size: var(--gn-font-size, var(--gonavi-font-size, 14px));
-webkit-font-smoothing: antialiased;
@@ -429,9 +433,9 @@ body[data-ui-version="v2"] .ant-dropdown-menu,
body[data-ui-version="v2"] .ant-picker-dropdown .ant-picker-panel-container {
background: var(--gn-bg-panel) !important;
border: 0.5px solid var(--gn-br-2) !important;
- border-radius: 10px !important;
+ border-radius: var(--gn-v2-menu-surface-radius) !important;
box-shadow: var(--gn-shadow-lg) !important;
- padding: 4px !important;
+ padding: var(--gn-v2-menu-surface-padding) !important;
}
body[data-ui-version="v2"] .ant-dropdown-menu-item,
@@ -452,6 +456,17 @@ body[data-ui-version="v2"] .ant-select-item {
outline: none !important;
}
+/* Compact action menus share the table context-menu geometry. */
+body[data-ui-version="v2"] .ant-dropdown-menu-item,
+body[data-ui-version="v2"] .ant-dropdown-menu-submenu-title {
+ height: var(--gn-v2-menu-row-height) !important;
+ min-height: var(--gn-v2-menu-row-height) !important;
+ box-sizing: border-box !important;
+ padding: 4px 8px !important;
+ border-radius: var(--gn-v2-menu-item-radius) !important;
+ font-weight: 500 !important;
+}
+
/* Icon + label share one baseline/vertical center (leaf and submenu rows) */
body[data-ui-version="v2"] .ant-dropdown-menu-title-content {
display: inline-flex !important;
@@ -501,12 +516,12 @@ body[data-ui-version="v2"] .ant-dropdown-menu-submenu-title .ant-dropdown-menu-i
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
- flex: 0 0 16px !important;
- width: 16px !important;
- height: 16px !important;
+ flex: 0 0 14px !important;
+ width: 14px !important;
+ height: 14px !important;
margin-inline-end: 8px !important;
margin-right: 8px !important;
- font-size: 14px !important;
+ font-size: 13px !important;
line-height: 1 !important;
}
@@ -536,6 +551,63 @@ body[data-ui-version="v2"] .ant-dropdown-menu-item-danger:hover {
background: rgba(220, 38, 38, 0.08) !important;
}
+/* Monaco context menu follows the same compact action-menu surface. */
+body[data-ui-version="v2"] .monaco-menu {
+ min-width: 264px !important;
+ overflow: hidden !important;
+ padding: var(--gn-v2-menu-surface-padding) !important;
+ border: 0.5px solid var(--gn-br-2) !important;
+ border-radius: var(--gn-v2-menu-surface-radius) !important;
+ background: var(--gn-bg-panel) !important;
+ box-shadow: var(--gn-shadow-lg) !important;
+}
+
+body[data-ui-version="v2"] .monaco-menu > .gn-v2-monaco-context-menu-header {
+ margin: calc(var(--gn-v2-menu-surface-padding) * -1) calc(var(--gn-v2-menu-surface-padding) * -1) 4px;
+ border-radius: calc(var(--gn-v2-menu-surface-radius) - 1px) calc(var(--gn-v2-menu-surface-radius) - 1px) 0 0;
+ font-family: var(--gn-font-sans);
+}
+
+body[data-ui-version="v2"] .gn-v2-monaco-context-menu-icon {
+ width: 18px;
+ color: var(--gn-info);
+ font-family: var(--gn-font-mono);
+ font-size: 8px;
+ font-weight: 700;
+ letter-spacing: -0.04em;
+}
+
+body[data-ui-version="v2"] .monaco-menu-option,
+body[data-ui-version="v2"] .monaco-menu .monaco-action-bar.vertical .action-menu-item,
+body[data-ui-version="v2"] .monaco-menu .monaco-action-bar.vertical .action-label:not(.separator) {
+ min-height: var(--gn-v2-menu-row-height) !important;
+ border-radius: var(--gn-v2-menu-item-radius) !important;
+ font-size: 12.5px !important;
+ line-height: var(--gn-v2-menu-row-height) !important;
+}
+
+body[data-ui-version="v2"] .monaco-menu-option,
+body[data-ui-version="v2"] .monaco-menu .monaco-action-bar.vertical .action-label:not(.separator) {
+ padding: 0 8px !important;
+}
+
+body[data-ui-version="v2"] .monaco-menu-option.active,
+body[data-ui-version="v2"] .monaco-menu .monaco-action-bar.vertical .action-menu-item:focus {
+ background: var(--gn-bg-active) !important;
+ color: var(--gn-fg-1) !important;
+ outline: none !important;
+}
+
+body[data-ui-version="v2"] .monaco-menu .monaco-action-bar .action-item .action-label.separator {
+ width: auto !important;
+ height: 1px !important;
+ min-height: 1px !important;
+ margin: 4px !important;
+ padding: 0 !important;
+ border: 0 !important;
+ background: var(--gn-br-1) !important;
+}
+
/* Nested dropdown menus: collapse double border, no glow/halo */
body[data-ui-version="v2"] .ant-dropdown-menu-submenu-popup,
body[data-ui-version="v2"] .gn-v2-titlebar-quick-submenu,
@@ -575,7 +647,7 @@ body[data-ui-version="v2"] .gn-v2-titlebar-quick-menu .ant-dropdown-menu-submenu
background: transparent !important;
border: none !important;
box-shadow: none !important;
- min-height: 32px !important;
+ min-height: var(--gn-v2-menu-row-height) !important;
}
body[data-ui-version="v2"] .gn-v2-titlebar-quick-dropdown .ant-dropdown-menu-item:hover,
@@ -4314,26 +4386,6 @@ body[data-ui-version="v2"] .gn-v2-tab-close:hover {
body[data-ui-version="v2"] .gn-v2-tab-context-menu-popup .ant-dropdown-menu {
min-width: 188px;
- padding: 6px !important;
- border: 0.5px solid var(--gn-br-2) !important;
- border-radius: 12px !important;
- background: color-mix(in srgb, var(--gn-bg-panel) 94%, transparent) !important;
- box-shadow: 0 18px 44px rgba(15, 23, 42, 0.18), 0 0 0 1px rgba(15, 23, 42, 0.04) !important;
- backdrop-filter: blur(12px);
-}
-
-body[data-ui-version="v2"] .gn-v2-tab-context-menu-popup .ant-dropdown-menu-item {
- min-height: 34px !important;
- padding: 7px 10px !important;
- border-radius: 8px !important;
- color: var(--gn-fg-2) !important;
- font-size: 13px !important;
- font-weight: 650 !important;
-}
-
-body[data-ui-version="v2"] .gn-v2-tab-context-menu-popup .ant-dropdown-menu-item:hover {
- background: var(--gn-bg-active) !important;
- color: var(--gn-fg-1) !important;
}
body[data-ui-version="v2"] .gn-v2-tab-context-menu-popup .ant-dropdown-menu-item-disabled {
@@ -4341,30 +4393,11 @@ body[data-ui-version="v2"] .gn-v2-tab-context-menu-popup .ant-dropdown-menu-item
opacity: 0.5;
}
-body[data-ui-version="v2"] .gn-v2-tab-context-menu-popup .ant-dropdown-menu-item-icon {
- color: var(--gn-info);
- font-size: 14px;
-}
-
-body[data-ui-version="v2"] .gn-v2-tab-context-menu-popup .ant-dropdown-menu-item-divider {
- margin: 5px 4px !important;
- background: var(--gn-br-1) !important;
-}
-
body[data-ui-version="v2"] .gn-v2-tab-context-menu-popup .ant-dropdown-menu-title-content {
display: inline-flex;
align-items: center;
}
-body[data-ui-version="v2"] .gn-v2-tab-context-menu-popup .ant-dropdown-menu-item:first-child {
- color: var(--gn-fg-1) !important;
- background: color-mix(in srgb, var(--gn-info) 8%, transparent) !important;
-}
-
-body[data-ui-version="v2"] .gn-v2-tab-context-menu-popup .ant-dropdown-menu-item:first-child:hover {
- background: color-mix(in srgb, var(--gn-info) 14%, transparent) !important;
-}
-
/* ─── V2 DataGrid: toolbar, smart filters, table, statusbar ─ */
body[data-ui-version="v2"] .gn-v2-data-viewer {
background: var(--gn-bg-panel);
@@ -5760,11 +5793,85 @@ body[data-ui-version="v2"] .gn-v2-data-grid-pagination-wrap .data-grid-paginatio
}
/* ─── V2 context menus, including portal-rendered custom menus ─ */
+body[data-ui-version="v2"] .gn-v2-action-menu-surface {
+ width: 264px;
+ max-width: calc(100vw - 24px);
+ overflow: hidden;
+ box-sizing: border-box;
+ border: 0.5px solid var(--gn-br-2);
+ border-radius: var(--gn-v2-menu-surface-radius);
+ background: var(--gn-bg-panel);
+ color: var(--gn-fg-1);
+ box-shadow: var(--gn-shadow-lg);
+ font-size: 12.5px;
+ animation: gnV2ContextMenuFadeIn 0.08s ease-out;
+}
+
+body[data-ui-version="v2"] .gn-v2-action-menu-surface .gn-v2-action-menu-header {
+ border-radius: calc(var(--gn-v2-menu-surface-radius) - 1px) calc(var(--gn-v2-menu-surface-radius) - 1px) 0 0;
+}
+
+body[data-ui-version="v2"] .gn-v2-action-menu-header-icon:empty::before {
+ content: "\2022";
+ color: var(--gn-fg-5);
+}
+
+body[data-ui-version="v2"] .gn-v2-action-menu-body {
+ overflow: hidden;
+ padding: 4px 4px 8px;
+}
+
+body[data-ui-version="v2"] .gn-v2-action-menu-body > .ant-dropdown-menu {
+ width: 100%;
+ min-width: 0;
+ padding: 0 !important;
+ border: 0 !important;
+ border-radius: 0 !important;
+ background: transparent !important;
+ box-shadow: none !important;
+}
+
+body[data-ui-version="v2"] .gn-v2-action-menu-body .ant-dropdown-menu-item,
+body[data-ui-version="v2"] .gn-v2-action-menu-body .ant-dropdown-menu-submenu-title {
+ padding: 0 8px 0 10px !important;
+}
+
+body[data-ui-version="v2"] .gn-v2-action-menu-body .ant-dropdown-menu-item-group-title {
+ padding: 8px 12px 2px !important;
+ color: var(--gn-fg-5) !important;
+ font-size: 9.5px !important;
+ font-weight: 700 !important;
+ line-height: 1.25 !important;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+}
+
+body[data-ui-version="v2"] .gn-v2-action-menu-body .ant-dropdown-menu-item-group-list {
+ margin: 0 !important;
+}
+
+body[data-ui-version="v2"] .gn-v2-action-menu-body .ant-dropdown-menu-title-content > .gn-v2-context-menu-item-title {
+ width: 100%;
+ display: inline-flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 8px;
+}
+
+body[data-ui-version="v2"] .gn-v2-action-menu-body .ant-dropdown-menu-item:not(:has(.ant-dropdown-menu-item-icon))::before,
+body[data-ui-version="v2"] .gn-v2-action-menu-body .ant-dropdown-menu-submenu-title:not(:has(.ant-dropdown-menu-item-icon))::before {
+ content: "";
+ width: 14px;
+ height: 14px;
+ flex: 0 0 14px;
+ margin-inline-end: 8px;
+}
+
body[data-ui-version="v2"] .gn-v2-context-menu {
width: 260px !important;
min-width: 220px !important;
border: 0.5px solid var(--gn-br-2) !important;
- border-radius: 10px !important;
+ border-radius: var(--gn-v2-menu-surface-radius) !important;
background: var(--gn-bg-panel) !important;
box-shadow: var(--gn-shadow-lg) !important;
color: var(--gn-fg-1) !important;
@@ -5807,7 +5914,7 @@ body[data-ui-version="v2"] .gn-v2-table-context-menu {
overflow: hidden;
padding: 0;
border: 0.5px solid var(--gn-br-2);
- border-radius: 10px;
+ border-radius: var(--gn-v2-menu-surface-radius);
background: var(--gn-bg-panel);
color: var(--gn-fg-1);
box-shadow: var(--gn-shadow-lg);
@@ -5926,12 +6033,12 @@ body[data-ui-version="v2"] .gn-v2-context-menu-divider {
body[data-ui-version="v2"] .gn-v2-context-menu-item {
width: 100%;
- height: 28px;
+ height: var(--gn-v2-menu-row-height);
display: flex;
gap: 8px;
align-items: center;
border: 0;
- border-radius: 5px;
+ border-radius: var(--gn-v2-menu-item-radius);
padding: 0 8px 0 10px;
background: transparent;
color: var(--gn-fg-1);