diff --git a/packages/compass-components/src/components/context-menu.spec.tsx b/packages/compass-components/src/components/context-menu.spec.tsx index 6319d9c07cc..ac6a2b23882 100644 --- a/packages/compass-components/src/components/context-menu.spec.tsx +++ b/packages/compass-components/src/components/context-menu.spec.tsx @@ -3,8 +3,11 @@ import { render, screen, userEvent } from '@mongodb-js/testing-library-compass'; import { expect } from 'chai'; import sinon from 'sinon'; import { ContextMenuProvider } from '@mongodb-js/compass-context-menu'; -import { useContextMenuItems, ContextMenu } from './context-menu'; -import type { ContextMenuItem } from '@mongodb-js/compass-context-menu'; +import { + useContextMenuItems, + ContextMenu, + type ContextMenuItem, +} from './context-menu'; describe('useContextMenuItems', function () { const menuTestTriggerId = 'test-trigger'; diff --git a/packages/compass-components/src/components/context-menu.tsx b/packages/compass-components/src/components/context-menu.tsx index e1931bdab1c..c8c01ea7ae3 100644 --- a/packages/compass-components/src/components/context-menu.tsx +++ b/packages/compass-components/src/components/context-menu.tsx @@ -117,22 +117,41 @@ export function ContextMenu({ menu }: ContextMenuWrapperProps) { ); } +/** Registers context menu items - items that are `undefined` will get filtered. */ export function useContextMenuItems( - getItems: () => ContextMenuItem[], + getItems: () => (ContextMenuItem | undefined)[], dependencies: React.DependencyList | undefined ): React.RefCallback { - // eslint-disable-next-line react-hooks/exhaustive-deps - const memoizedItems = useMemo(getItems, dependencies); + const memoizedItems = useMemo( + () => + getItems().filter((item): item is ContextMenuItem => item !== undefined), + // eslint-disable-next-line react-hooks/exhaustive-deps + dependencies + ); const contextMenu = useContextMenu(); return contextMenu.registerItems(memoizedItems); } +/** Registers context menu groups - groups and items that are `undefined` will get filtered. */ export function useContextMenuGroups( - getGroups: () => ContextMenuItemGroup[], + getGroups: () => ((ContextMenuItem | undefined)[] | undefined)[], dependencies: React.DependencyList | undefined ): React.RefCallback { - // eslint-disable-next-line react-hooks/exhaustive-deps - const memoizedGroups = useMemo(getGroups, dependencies); + const memoizedGroups: ContextMenuItem[][] = useMemo( + () => { + const groups = getGroups(); + // Cleanup all undefined fields across items and groups which is used + // for conditional displaying of groups and items. + return groups + .filter( + (groupItems): groupItems is ContextMenuItem[] => + groupItems !== undefined && groupItems.length > 0 + ) + .map((groupItems) => groupItems.filter((item) => item !== undefined)); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + dependencies + ); const contextMenu = useContextMenu(); return contextMenu.registerItems(...memoizedGroups); } diff --git a/packages/compass-components/src/components/document-list/element.tsx b/packages/compass-components/src/components/document-list/element.tsx index d3f07e63cee..83973ce71d5 100644 --- a/packages/compass-components/src/components/document-list/element.tsx +++ b/packages/compass-components/src/components/document-list/element.tsx @@ -501,24 +501,22 @@ export const HadronElement: React.FunctionComponent<{ // Add context menu hook for the field const fieldContextMenuRef = useContextMenuItems( () => [ - ...(onUpdateQuery - ? [ - { - label: isFieldInQuery( + onUpdateQuery + ? { + label: isFieldInQuery( + getNestedKeyPathForElement(element), + element.generateObject() + ) + ? 'Remove from query' + : 'Add to query', + onAction: () => { + onUpdateQuery( getNestedKeyPathForElement(element), element.generateObject() - ) - ? 'Remove from query' - : 'Add to query', - onAction: () => { - onUpdateQuery( - getNestedKeyPathForElement(element), - element.generateObject() - ); - }, + ); }, - ] - : []), + } + : undefined, { label: 'Copy field & value', onAction: () => { @@ -527,16 +525,14 @@ export const HadronElement: React.FunctionComponent<{ ); }, }, - ...(type.value === 'String' && isValidUrl(value.value) - ? [ - { - label: 'Open URL in browser', - onAction: () => { - window.open(value.value, '_blank', 'noopener'); - }, + type.value === 'String' && isValidUrl(value.value) + ? { + label: 'Open URL in browser', + onAction: () => { + window.open(value.value, '_blank', 'noopener'); }, - ] - : []), + } + : undefined, ], [element, key.value, value.value, type.value, onUpdateQuery, isFieldInQuery] ); diff --git a/packages/compass-crud/src/components/crud-toolbar.tsx b/packages/compass-crud/src/components/crud-toolbar.tsx index 84ce2603d39..e1d56e389d8 100644 --- a/packages/compass-crud/src/components/crud-toolbar.tsx +++ b/packages/compass-crud/src/components/crud-toolbar.tsx @@ -217,26 +217,22 @@ const CrudToolbar: React.FunctionComponent = ({ onCollapseAllClicked(); }, }, - ...(isImportExportEnabled - ? [ - { - label: 'Import JSON or CSV file', - onAction: () => { - insertDataHandler('import-file'); - }, + isImportExportEnabled + ? { + label: 'Import JSON or CSV file', + onAction: () => { + insertDataHandler('import-file'); }, - ] - : []), - ...(!readonly - ? [ - { - label: 'Insert document...', - onAction: () => { - insertDataHandler('insert-document'); - }, + } + : undefined, + !readonly + ? { + label: 'Insert document...', + onAction: () => { + insertDataHandler('insert-document'); }, - ] - : []), + } + : undefined, ...(isImportExportEnabled ? [ { diff --git a/packages/compass-crud/src/components/use-document-item-context-menu.tsx b/packages/compass-crud/src/components/use-document-item-context-menu.tsx index 67d9689ee1c..db3c9c547d0 100644 --- a/packages/compass-crud/src/components/use-document-item-context-menu.tsx +++ b/packages/compass-crud/src/components/use-document-item-context-menu.tsx @@ -15,24 +15,23 @@ export function useDocumentItemContextMenu({ openInsertDocumentDialog, }: UseDocumentItemContextMenuProps) { const { expanded: isExpanded, editing: isEditing } = doc; + return useContextMenuGroups( () => [ - [ - ...(isEditable - ? [ - { - label: isEditing ? 'Cancel editing' : 'Edit document', - onAction: () => { - if (isEditing) { - doc.finishEditing(); - } else { - doc.startEditing(); - } - }, + isEditable + ? [ + { + label: isEditing ? 'Cancel editing' : 'Edit document', + onAction: () => { + if (isEditing) { + doc.finishEditing(); + } else { + doc.startEditing(); + } }, - ] - : []), - ], + }, + ] + : undefined, [ { label: isExpanded ? 'Collapse all fields' : 'Expand all fields',