use context provider for handling context menu close
Signed-off-by: Mark Dunphy <markd@spotify.com>
This commit is contained in:
@@ -89,9 +89,7 @@ export const catalogReactTranslationRef: TranslationRef<
|
||||
>;
|
||||
|
||||
// @alpha (undocumented)
|
||||
export type ContextMenuItemComponent = (
|
||||
props: ContextMenuItemProps,
|
||||
) => React_2.JSX.Element;
|
||||
export type ContextMenuItemComponent = (props: {}) => React_2.JSX.Element;
|
||||
|
||||
// @alpha (undocumented)
|
||||
export const contextMenuItemComponentDataRef: ConfigurableExtensionDataRef<
|
||||
@@ -100,11 +98,6 @@ export const contextMenuItemComponentDataRef: ConfigurableExtensionDataRef<
|
||||
{}
|
||||
>;
|
||||
|
||||
// @alpha (undocumented)
|
||||
export type ContextMenuItemProps = {
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
// @alpha (undocumented)
|
||||
export function convertLegacyEntityCardExtension(
|
||||
LegacyExtension: ComponentType<{}>,
|
||||
|
||||
@@ -243,6 +243,24 @@ export type EntityAutocompletePickerProps<
|
||||
hidden?: boolean;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type EntityContextMenuContextValue = {
|
||||
onMenuClose: () => void;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntityContextMenuProvider: (
|
||||
props: EntityContextMenuProviderProps,
|
||||
) => React_2.JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface EntityContextMenuProviderProps {
|
||||
// (undocumented)
|
||||
children: React_2.ReactNode;
|
||||
// (undocumented)
|
||||
onMenuClose: () => void;
|
||||
}
|
||||
|
||||
// @public
|
||||
export const EntityDisplayName: (props: EntityDisplayNameProps) => JSX.Element;
|
||||
|
||||
@@ -778,6 +796,9 @@ export function useEntity<TEntity extends Entity = Entity>(): {
|
||||
entity: TEntity;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export function useEntityContextMenu(): EntityContextMenuContextValue;
|
||||
|
||||
// @public
|
||||
export function useEntityList<
|
||||
EntityFilters extends DefaultEntityFilters = DefaultEntityFilters,
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
import MenuItem from '@material-ui/core/MenuItem';
|
||||
import ListItemIcon from '@material-ui/core/ListItemIcon';
|
||||
import ListItemText from '@material-ui/core/ListItemText';
|
||||
import { useEntityContextMenu } from '../../hooks/useEntityContextMenu';
|
||||
|
||||
/** @alpha */
|
||||
export type UseProps = () =>
|
||||
@@ -44,14 +45,7 @@ export type EntityContextMenuItemParams = {
|
||||
};
|
||||
|
||||
/** @alpha */
|
||||
export type ContextMenuItemProps = {
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
/** @alpha */
|
||||
export type ContextMenuItemComponent = (
|
||||
props: ContextMenuItemProps,
|
||||
) => React.JSX.Element;
|
||||
export type ContextMenuItemComponent = (props: {}) => React.JSX.Element;
|
||||
|
||||
/** @alpha */
|
||||
export const contextMenuItemComponentDataRef =
|
||||
@@ -66,7 +60,8 @@ export const EntityContextMenuItemBlueprint = createExtensionBlueprint({
|
||||
output: [contextMenuItemComponentDataRef],
|
||||
*factory(params: EntityContextMenuItemParams, { node }) {
|
||||
const loader = async (): Promise<ContextMenuItemComponent> => {
|
||||
return ({ onClose }) => {
|
||||
return () => {
|
||||
const { onMenuClose } = useEntityContextMenu();
|
||||
const { title, ...menuItemProps } = params.useProps();
|
||||
let handleClick = undefined;
|
||||
|
||||
@@ -74,9 +69,9 @@ export const EntityContextMenuItemBlueprint = createExtensionBlueprint({
|
||||
handleClick = () => {
|
||||
const result = menuItemProps.onClick();
|
||||
if (result && 'finally' in result) {
|
||||
result.finally(onClose);
|
||||
result.finally(onMenuClose);
|
||||
} else {
|
||||
onClose();
|
||||
onMenuClose();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ export type { EntityCardType } from './extensionData';
|
||||
export {
|
||||
contextMenuItemComponentDataRef,
|
||||
EntityContextMenuItemBlueprint,
|
||||
type ContextMenuItemProps,
|
||||
type ContextMenuItemComponent,
|
||||
type EntityContextMenuItemParams,
|
||||
type UseProps,
|
||||
|
||||
@@ -35,6 +35,15 @@ export type {
|
||||
EntityListProviderProps,
|
||||
PaginationMode,
|
||||
} from './useEntityListProvider';
|
||||
export type {
|
||||
EntityContextMenuContextValue,
|
||||
EntityContextMenuProviderProps,
|
||||
} from './useEntityContextMenu';
|
||||
export {
|
||||
EntityContextMenuProvider,
|
||||
useEntityContextMenu,
|
||||
} from './useEntityContextMenu';
|
||||
|
||||
export { useEntityTypeFilter } from './useEntityTypeFilter';
|
||||
export { useRelatedEntities } from './useRelatedEntities';
|
||||
export { useStarredEntities } from './useStarredEntities';
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import {
|
||||
EntityContextMenuProvider,
|
||||
useEntityContextMenu,
|
||||
} from './useEntityContextMenu';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
|
||||
describe('useEntityContextMenu', () => {
|
||||
it('should throw error when used outside of provider', () => {
|
||||
expect(() => {
|
||||
renderHook(() => useEntityContextMenu());
|
||||
}).toThrow(
|
||||
'useEntityContextMenu must be used within an EntityContextMenuProvider',
|
||||
);
|
||||
});
|
||||
|
||||
it('should return the context value', () => {
|
||||
const mockOnMenuClose = jest.fn();
|
||||
const { result } = renderHook(() => useEntityContextMenu(), {
|
||||
wrapper: ({ children }) => (
|
||||
<EntityContextMenuProvider onMenuClose={mockOnMenuClose}>
|
||||
{children}
|
||||
</EntityContextMenuProvider>
|
||||
),
|
||||
});
|
||||
|
||||
expect(result.current.onMenuClose).toBe(mockOnMenuClose);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import {
|
||||
createVersionedContext,
|
||||
createVersionedValueMap,
|
||||
useVersionedContext,
|
||||
} from '@backstage/version-bridge';
|
||||
|
||||
/** @public */
|
||||
export type EntityContextMenuContextValue = {
|
||||
onMenuClose: () => void;
|
||||
};
|
||||
|
||||
const EntityContextMenuContext = createVersionedContext<{
|
||||
1: EntityContextMenuContextValue;
|
||||
}>('entity-context-menu-context');
|
||||
|
||||
/** @public */
|
||||
export interface EntityContextMenuProviderProps {
|
||||
children: React.ReactNode;
|
||||
onMenuClose: () => void;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export const EntityContextMenuProvider = (
|
||||
props: EntityContextMenuProviderProps,
|
||||
) => {
|
||||
const { children, onMenuClose } = props;
|
||||
const value = { onMenuClose };
|
||||
|
||||
return (
|
||||
<EntityContextMenuContext.Provider
|
||||
value={createVersionedValueMap({ 1: value })}
|
||||
>
|
||||
{children}
|
||||
</EntityContextMenuContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export function useEntityContextMenu() {
|
||||
const versionedHolder = useVersionedContext<{
|
||||
1: EntityContextMenuContextValue;
|
||||
}>('entity-context-menu-context');
|
||||
|
||||
if (!versionedHolder) {
|
||||
throw new Error(
|
||||
'useEntityContextMenu must be used within an EntityContextMenuProvider',
|
||||
);
|
||||
}
|
||||
|
||||
const value = versionedHolder.atVersion(1);
|
||||
if (!value) {
|
||||
throw new Error('EntityContextMenu v1 is not available');
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
@@ -36,6 +36,7 @@ import useCopyToClipboard from 'react-use/esm/useCopyToClipboard';
|
||||
import { catalogTranslationRef } from '../../alpha/translation';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import type { ContextMenuItemComponent } from '@backstage/plugin-catalog-react/alpha';
|
||||
import { EntityContextMenuProvider } from '@backstage/plugin-catalog-react';
|
||||
|
||||
/** @public */
|
||||
export type EntityContextMenuClassKey = 'button';
|
||||
@@ -187,12 +188,11 @@ export function EntityContextMenu(props: EntityContextMenuProps) {
|
||||
</MenuItem>
|
||||
</>
|
||||
) : (
|
||||
contextMenuItems.map((ContextMenuItem, idx) => (
|
||||
<ContextMenuItem
|
||||
key={`context-menu-item-${idx}`}
|
||||
onClose={onClose}
|
||||
/>
|
||||
))
|
||||
<EntityContextMenuProvider onMenuClose={onClose}>
|
||||
{contextMenuItems.map((ContextMenuItem, idx) => (
|
||||
<ContextMenuItem key={`context-menu-item-${idx}`} />
|
||||
))}
|
||||
</EntityContextMenuProvider>
|
||||
)}
|
||||
</MenuList>
|
||||
</Popover>
|
||||
|
||||
Reference in New Issue
Block a user