Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import { Button, Icon, Menu, MenuItem, MenuSeparator } from '../leafygreen';
import { WorkspaceContainer } from '../workspace-container';

import { ItemActionButtonSize } from './constants';
import { actionTestId } from './utils';
import { ActionGlyph } from './action-glyph';
import { isSeparatorMenuAction, type MenuAction } from './item-action-menu';
import { actionTestId, isSeparatorMenuAction } from './utils';
import type { MenuAction } from './types';

const hiddenOnNarrowStyles = css({
[`@container ${WorkspaceContainer.toolbarContainerQueryName} (width < 900px)`]:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@ import { MenuSeparator, Tooltip } from '../leafygreen';

import { ItemActionButtonSize } from './constants';
import type { ItemAction, ItemSeparator } from './types';
import { isSeparatorMenuAction } from './item-action-menu';
import { ItemActionButton } from './item-action-button';
import { actionTestId } from './utils';
import { actionTestId, isSeparatorMenuAction } from './utils';

export type GroupedItemAction<Action extends string> = ItemAction<Action> & {
tooltipProps?: Parameters<typeof Tooltip>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,9 @@ import { Menu, MenuItem, MenuSeparator } from '../leafygreen';

import { ItemActionButtonSize } from './constants';
import { ActionGlyph } from './action-glyph';
import type { ItemBase, ItemSeparator } from './types';
import type { MenuAction } from './types';
import { SmallIconButton } from './small-icon-button';
import { actionTestId } from './utils';

export type MenuAction<Action extends string> =
| ItemBase<Action>
| ItemSeparator;

export function isSeparatorMenuAction(value: unknown): value is ItemSeparator {
return (
typeof value === 'object' &&
value !== null &&
'separator' in value &&
value.separator === true
);
}
import { actionTestId, isSeparatorMenuAction } from './utils';

const containerStyle = css({
flex: 'none',
Expand Down
4 changes: 4 additions & 0 deletions packages/compass-components/src/components/actions/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,7 @@ export type ItemAction<Action extends string> = {
} & ItemBase<Action>;

export type ItemSeparator = { separator: true };

export type MenuAction<Action extends string> =
| ItemBase<Action>
| ItemSeparator;
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { expect } from 'chai';
import { splitBySeparator } from './utils';

describe('item action utils', function () {
describe('splitBySeparator', function () {
it('returns an empty array for an empty input', function () {
const result = splitBySeparator([]);
expect(result).is.empty;
});

it('returns a single item for a single input', function () {
const result = splitBySeparator([{ label: 'Foo', action: 'foo' }]);
expect(result).deep.equal([[{ label: 'Foo', action: 'foo' }]]);
});

it('splits four items separated by a separator', function () {
const result = splitBySeparator([
{ label: 'Foo', action: 'foo' },
{ label: 'Bar', action: 'bar' },
{ separator: true },
{ label: 'Baz', action: 'baz' },
{ label: 'Qux', action: 'qux' },
]);
expect(result).deep.equal([
[
{ label: 'Foo', action: 'foo' },
{ label: 'Bar', action: 'bar' },
],
[
{ label: 'Baz', action: 'baz' },
{ label: 'Qux', action: 'qux' },
],
]);
});

it('disregards leading separators', function () {
const result = splitBySeparator([
{ separator: true },
{ label: 'Foo', action: 'foo' },
]);
expect(result).deep.equal([[{ label: 'Foo', action: 'foo' }]]);
});

it('disregards trailing separators', function () {
const result = splitBySeparator([
{ label: 'Foo', action: 'foo' },
{ separator: true },
]);
expect(result).deep.equal([[{ label: 'Foo', action: 'foo' }]]);
});
});
});
32 changes: 32 additions & 0 deletions packages/compass-components/src/components/actions/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,35 @@
import type { ItemBase, ItemSeparator, MenuAction } from './types';

export function isSeparatorMenuAction(value: unknown): value is ItemSeparator {
return (
typeof value === 'object' &&
value !== null &&
'separator' in value &&
value.separator === true
);
}

export function actionTestId(dataTestId: string | undefined, action: string) {
return dataTestId ? `${dataTestId}-${action}-action` : undefined;
}

export function splitBySeparator<Action extends string>(
actions: MenuAction<Action>[]
) {
const result: ItemBase<Action>[][] = [];
let currentGroup: ItemBase<Action>[] = [];
for (const action of actions) {
if (isSeparatorMenuAction(action)) {
if (currentGroup.length > 0) {
result.push(currentGroup);
currentGroup = [];
}
} else {
currentGroup.push(action);
}
}
if (currentGroup.length > 0) {
result.push(currentGroup);
}
return result;
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ import {
type ContextMenuWrapperProps,
} from '@mongodb-js/compass-context-menu';

export type { ContextMenuItem } from '@mongodb-js/compass-context-menu';
export type {
ContextMenuItem,
ContextMenuItemGroup,
} from '@mongodb-js/compass-context-menu';

// TODO: Remove these once https://jira.mongodb.org/browse/LG-5013 is resolved

Expand Down
25 changes: 9 additions & 16 deletions packages/compass-components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,18 +51,19 @@ import ResizableSidebar, {
defaultSidebarWidth,
} from './components/resizeable-sidebar';

import type {
export type {
ItemAction,
ItemComponentProps,
ItemSeparator,
MenuAction,
} from './components/actions/types';
import type { GroupedItemAction } from './components/actions/item-action-group';
import type { MenuAction } from './components/actions/item-action-menu';
export { splitBySeparator } from './components/actions/utils';
export type { GroupedItemAction } from './components/actions/item-action-group';

import { ItemActionControls } from './components/actions/item-action-controls';
import { ItemActionGroup } from './components/actions/item-action-group';
import { ItemActionMenu } from './components/actions/item-action-menu';
import { DropdownMenuButton } from './components/actions/dropdown-menu-button';
export { ItemActionControls } from './components/actions/item-action-controls';
export { ItemActionGroup } from './components/actions/item-action-group';
export { ItemActionMenu } from './components/actions/item-action-menu';
export { DropdownMenuButton } from './components/actions/dropdown-menu-button';

export { DocumentIcon } from './components/icons/document-icon';
export { FavoriteIcon } from './components/icons/favorite-icon';
Expand Down Expand Up @@ -104,15 +105,11 @@ export {
useContextMenuItems,
useContextMenuGroups,
type ContextMenuItem,
type ContextMenuItemGroup,
} from './components/context-menu';

export type {
FileInputBackend,
ItemAction,
ItemComponentProps,
GroupedItemAction,
MenuAction,
ItemSeparator,
ElectronFileDialogOptions,
ElectronShowFileDialogProvider,
};
Expand All @@ -131,10 +128,6 @@ export {
ResizableSidebar,
WarningSummary,
WorkspaceTabs,
ItemActionControls,
ItemActionGroup,
ItemActionMenu,
DropdownMenuButton,
defaultSidebarWidth,
createElectronFileInputBackend,
createJSDomFileInputDummyBackend,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import type {
SidebarTreeItem,
} from './tree-data';

type NavigationBaseItemProps = {
type NavigationBaseItemProps = React.PropsWithChildren<{
item: SidebarTreeItem;
name: string;
isActive: boolean;
Expand All @@ -40,7 +40,7 @@ type NavigationBaseItemProps = {
onAction: (action: Actions) => void;
};
toggleExpand: () => void;
};
}>;

const menuStyles = css({
width: '240px',
Expand Down Expand Up @@ -155,26 +155,33 @@ const ClusterStateBadgeWithTooltip: React.FunctionComponent<{
return null;
};

export const NavigationBaseItem: React.FC<NavigationBaseItemProps> = ({
item,
isActive,
actionProps,
name,
style,
icon,
dataAttributes,
isExpandVisible,
isExpandDisabled,
isExpanded,
isFocused,
hasDefaultAction,
toggleExpand,
children,
}) => {
export const NavigationBaseItem = React.forwardRef<
HTMLDivElement,
NavigationBaseItemProps
>(function NavigationBaseItem(
{
item,
isActive,
actionProps,
name,
style,
icon,
dataAttributes,
isExpandVisible,
isExpandDisabled,
isExpanded,
isFocused,
hasDefaultAction,
toggleExpand,
children,
},
ref
) {
const [hoverProps, isHovered] = useHoverState();

return (
<div
ref={ref}
data-testid="base-navigation-item"
className={cx(itemContainerStyles, {
[itemContainerWithActionStyles]: hasDefaultAction,
Expand Down Expand Up @@ -214,4 +221,4 @@ export const NavigationBaseItem: React.FC<NavigationBaseItemProps> = ({
</div>
</div>
);
};
});
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ import type {
SidebarActionableItem,
Connection,
} from './tree-data';
import type { ItemAction, ItemSeparator } from '@mongodb-js/compass-components';
import type {
ContextMenuItemGroup,
ItemAction,
ItemSeparator,
} from '@mongodb-js/compass-components';
import {
VisuallyHidden,
css,
Expand All @@ -28,6 +32,7 @@ import {
databaseItemActions,
notConnectedConnectionItemActions,
} from './item-actions';
import { itemActionsToContextMenuGroups } from './context-menus';

const ConnectionsNavigationContainerStyles = css({
display: 'flex',
Expand Down Expand Up @@ -218,6 +223,61 @@ const ConnectionsNavigationTree: React.FunctionComponent<
]
);

const getContextMenuGroups = useCallback(
function getContextMenuGroups(
item: SidebarTreeItem
): ContextMenuItemGroup[] {
switch (item.type) {
case 'placeholder':
return [];
case 'connection':
return itemActionsToContextMenuGroups(
item,
onItemAction,
item.connectionStatus === 'connected'
? connectedConnectionItemActions({
hasWriteActionsDisabled: item.hasWriteActionsDisabled,
isShellEnabled: item.isShellEnabled,
connectionInfo: item.connectionInfo,
isPerformanceTabAvailable: item.isPerformanceTabAvailable,
isPerformanceTabSupported: item.isPerformanceTabSupported,
})
: notConnectedConnectionItemActions({
connectionInfo: item.connectionInfo,
connectionStatus: item.connectionStatus,
})
);
case 'database':
return [
...itemActionsToContextMenuGroups(
item,
onItemAction,
databaseItemActions({
hasWriteActionsDisabled: item.hasWriteActionsDisabled,
})
),
// Include menu items on the connection level
...getContextMenuGroups(item.connectionItem),
];
default:
return [
...itemActionsToContextMenuGroups(
item,
onItemAction,
collectionItemActions({
hasWriteActionsDisabled: item.hasWriteActionsDisabled,
type: item.type,
isRenameCollectionEnabled,
})
),
// Include menu items on the database (and connection) level
...getContextMenuGroups(item.databaseItem),
];
}
},
[onItemAction]
);

const isTestEnv = process.env.NODE_ENV === 'test';

return (
Expand All @@ -242,13 +302,15 @@ const ConnectionsNavigationTree: React.FunctionComponent<
onItemExpand={onItemExpand}
getItemActions={getItemActionsAndConfig}
getItemKey={(item) => item.id}
getContextMenuGroups={getContextMenuGroups}
renderItem={({
item,
isActive,
isFocused,
onItemAction,
onItemExpand,
getItemActions,
getContextMenuGroups,
}) => {
return (
<NavigationItem
Expand All @@ -258,6 +320,7 @@ const ConnectionsNavigationTree: React.FunctionComponent<
getItemActions={getItemActions}
onItemExpand={onItemExpand}
onItemAction={onItemAction}
getContextMenuGroups={getContextMenuGroups}
/>
);
}}
Expand Down
Loading
Loading