refactor(core-ui): CB-2038 use menu blocks in context menu

This commit is contained in:
Wroud
2022-06-18 17:40:34 +03:00
parent 51a4338a0e
commit ee5e95b96d
12 changed files with 156 additions and 449 deletions
+23 -4
View File
@@ -16,28 +16,36 @@ import { ComponentStyle, useStyles } from '@cloudbeaver/core-theming';
import { useObjectRef } from '../useObjectRef';
import { MenuPanel } from './MenuPanel';
import { menuPanelStyles } from './menuPanelStyles';
import { MenuStateContext } from './MenuStateContext';
import { IMenuState, MenuStateContext } from './MenuStateContext';
interface IMenuProps extends Omit<React.ButtonHTMLAttributes<any>, 'style'> {
label: string;
items: React.ReactNode;
items: React.ReactNode | (() => React.ReactNode);
menuRef?: React.RefObject<IMenuState | undefined>;
style?: ComponentStyle;
disclosure?: boolean;
placement?: MenuInitialState['placement'];
modal?: boolean;
visible?: boolean;
rtl?: boolean;
hasBindings?: boolean;
panelAvailable?: boolean;
getHasBindings?: () => boolean;
onVisibleSwitch?: (visible: boolean) => void;
}
export const Menu = observer<IMenuProps, React.ButtonHTMLAttributes<any>>(function Menu({
export const Menu = observer<IMenuProps, HTMLButtonElement>(function Menu({
label,
items,
menuRef,
disclosure,
children,
style,
placement,
visible,
hasBindings,
panelAvailable,
getHasBindings,
onVisibleSwitch,
modal,
rtl,
@@ -47,6 +55,11 @@ export const Menu = observer<IMenuProps, React.ButtonHTMLAttributes<any>>(functi
const menu = useMenuState({ modal, placement, visible, rtl });
const styles = useStyles(menuPanelStyles, style);
if (menuRef) {
//@ts-expect-error Ref mutation
menuRef.current = menu;
}
useEffect(() => {
propsRef.onVisibleSwitch?.(menu.visible);
}, [menu.visible]);
@@ -63,6 +76,9 @@ export const Menu = observer<IMenuProps, React.ButtonHTMLAttributes<any>>(functi
menu={menu}
style={style}
rtl={rtl}
panelAvailable={panelAvailable}
hasBindings={hasBindings}
getHasBindings={getHasBindings}
>
{items}
</MenuPanel>
@@ -72,7 +88,7 @@ export const Menu = observer<IMenuProps, React.ButtonHTMLAttributes<any>>(functi
return styled(styles)(
<MenuStateContext.Provider value={menu}>
<MenuButton {...menu} {...props}>
<MenuButton ref={ref} {...menu} {...props}>
<box>{children}</box>
</MenuButton>
<MenuPanel
@@ -80,6 +96,9 @@ export const Menu = observer<IMenuProps, React.ButtonHTMLAttributes<any>>(functi
menu={menu}
style={style}
rtl={rtl}
panelAvailable={panelAvailable}
hasBindings={hasBindings}
getHasBindings={getHasBindings}
>
{items}
</MenuPanel>
@@ -13,6 +13,7 @@ import styled, { use } from 'reshadow';
import { ComponentStyle, useStyles } from '@cloudbeaver/core-theming';
import { getComputed } from '../getComputed';
import { MenuEmptyItem } from './MenuEmptyItem';
import { menuPanelStyles } from './menuPanelStyles';
@@ -22,18 +23,23 @@ export interface IMenuPanelProps {
menu: MenuStateReturn; // from reakit useMenuState
panelAvailable?: boolean;
hasBindings?: boolean;
getHasBindings?: () => boolean;
children: React.ReactNode | (() => React.ReactNode);
rtl?: boolean;
style?: ComponentStyle;
className?: string;
}
export const MenuPanel = observer<React.PropsWithChildren<IMenuPanelProps>>(function MenuPanel({
export const MenuPanel = observer<IMenuPanelProps>(function MenuPanel({
label,
menu,
panelAvailable = true,
hasBindings,
rtl,
getHasBindings,
hasBindings,
children,
style,
className,
}) {
const styles = useStyles(menuPanelStyles, style);
const visible = menu.visible;
@@ -42,13 +48,23 @@ export const MenuPanel = observer<React.PropsWithChildren<IMenuPanelProps>>(func
return null;
}
hasBindings = panelAvailable && (hasBindings || getComputed(() => getHasBindings?.()));
let renderedChildren: React.ReactNode = <></>;
if (panelAvailable) {
renderedChildren = typeof children === 'function'
? children()
: children;
}
return styled(styles)(
<Menu {...menu} aria-label={label} visible={panelAvailable}>
<Menu {...menu} aria-label={label} className={className} visible={panelAvailable}>
<menu-box dir={rtl ? 'rtl' : undefined} {...use({ hasBindings })}>
{Children.count(children) === 0 && (
{Children.count(renderedChildren) === 0 && (
<MenuEmptyItem style={style} />
)}
{children}
{renderedChildren}
</menu-box>
</Menu>
);
@@ -9,4 +9,6 @@
import { createContext } from 'react';
import type { MenuStateReturn } from 'reakit/ts';
export const MenuStateContext = createContext<MenuStateReturn | null>(null);
export type IMenuState = MenuStateReturn;
export const MenuStateContext = createContext<IMenuState | null>(null);
@@ -7,18 +7,16 @@
*/
import { observer } from 'mobx-react-lite';
import React, { ButtonHTMLAttributes, useCallback, useEffect } from 'react';
import { MenuButton, MenuInitialState, useMenuState } from 'reakit/Menu';
import { ButtonHTMLAttributes, useRef } from 'react';
import type { MenuInitialState } from 'reakit/Menu';
import styled from 'reshadow';
import { getComputed, useObjectRef } from '@cloudbeaver/core-blocks';
import { getComputed, IMenuState, Menu, menuPanelStyles, useObjectRef } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { ComponentStyle, useStyles } from '@cloudbeaver/core-theming';
import { IMenuData, MenuService } from '@cloudbeaver/core-view';
import { IMenuData, MenuActionItem, MenuService } from '@cloudbeaver/core-view';
import { MenuItemRenderer } from './MenuItemRenderer';
import { MenuPanel } from './MenuPanel';
import { menuPanelStyles } from './menuPanelStyles';
interface IMenuProps extends React.PropsWithChildren {
loading: boolean;
@@ -39,7 +37,7 @@ interface IContextMenuProps extends Omit<ButtonHTMLAttributes<any>, 'style' | 'c
onVisibleSwitch?: (visible: boolean) => void;
}
export const ContextMenu = observer<IContextMenuProps, ButtonHTMLAttributes<any>>(function ContextMenu({
export const ContextMenu = observer<IContextMenuProps, HTMLButtonElement>(function ContextMenu({
menu: menuData,
disclosure,
children,
@@ -59,80 +57,63 @@ export const ContextMenu = observer<IContextMenuProps, ButtonHTMLAttributes<any>
const disabled = getComputed(() => loading || handler?.isDisabled?.(menuData.context) || false);
const lazy = getComputed(() => !menuData.available || hidden);
const propsRef = useObjectRef({ onVisibleSwitch, visible });
const menu = useMenuState({ modal, placement, visible, rtl });
const menu = useRef<IMenuState>();
const styles = useStyles(menuPanelStyles, style);
const handleItemClose = useCallback(() => {
menu.hide();
}, [menu]);
const handlers = useObjectRef(() => ({
handleItemClose() {
menu.current?.hide();
},
hasBindings() {
return this.menuData.items.some(
item => item instanceof MenuActionItem && item.action.binding !== null
);
},
handleVisibleSwitch(visible: boolean) {
this.onVisibleSwitch?.(visible);
useEffect(() => {
propsRef.onVisibleSwitch?.(menu.visible);
if (menu.visible) {
handler?.handler?.(menuData.context);
}
}, [menu.visible]);
if (visible) {
this.handler?.handler?.(this.menuData.context);
}
},
}), { menuData, handler, onVisibleSwitch }, ['handleItemClose', 'hasBindings', 'handleVisibleSwitch']);
if (lazy) {
return null;
}
// TODO: fix type error
const renderingChildren: React.ReactNode = typeof children === 'function'
? (children as any)({ loading, disabled })
? children({ loading, disabled })
: children;
if (React.isValidElement(renderingChildren) && disclosure) {
return styled(styles)(
<>
<MenuButton ref={ref} {...menu} {...props} {...renderingChildren.props} disabled={disabled}>
{disclosureProps => React.cloneElement(renderingChildren, { ...disclosureProps, ...renderingChildren.props })}
</MenuButton>
<MenuPanel
menuData={menuData}
menu={menu}
style={style}
rtl={rtl}
>
{item => (
<MenuItemRenderer
key={item.id}
item={item}
menuData={menuData}
menu={menu}
style={style}
onItemClose={handleItemClose}
/>
)}
</MenuPanel>
</>
);
}
return styled(styles)(
<>
<MenuButton {...menu} {...props} disabled={disabled}>
<box>{renderingChildren}</box>
</MenuButton>
<MenuPanel
menuData={menuData}
menu={menu}
style={style}
rtl={rtl}
>
{item => (
<MenuItemRenderer
key={item.id}
item={item}
menuData={menuData}
menu={menu}
style={style}
onItemClose={handleItemClose}
/>
)}
</MenuPanel>
</>
<Menu
ref={ref}
label={menuData.menu.label}
items={() => menuData.items.map(item => menu.current && (
<MenuItemRenderer
key={item.id}
item={item}
menuData={menuData}
rtl={rtl}
menu={menu.current}
style={style}
onItemClose={handlers.handleItemClose}
/>
))}
rtl={rtl}
menuRef={menu}
modal={modal}
visible={visible}
placement={placement}
disabled={disabled}
disclosure={disclosure}
style={style}
getHasBindings={handlers.hasBindings}
onVisibleSwitch={handlers.handleVisibleSwitch}
{...props}
>
{renderingChildren}
</Menu>
);
}, { forwardRef: true });
@@ -11,13 +11,10 @@ import React from 'react';
import { MenuItem, MenuItemCheckbox, MenuItemRadio, MenuStateReturn } from 'reakit/Menu';
import styled, { use } from 'reshadow';
import { Checkbox, Radio } from '@cloudbeaver/core-blocks';
import { Checkbox, MenuItemElement, menuPanelStyles, Radio } from '@cloudbeaver/core-blocks';
import { ComponentStyle, useStyles } from '@cloudbeaver/core-theming';
import type { IMenuActionItem } from '@cloudbeaver/core-view';
import { MenuItemElement } from './MenuItemElement';
import { menuPanelStyles } from './menuPanelStyles';
interface IMenuActionElementProps {
item: IMenuActionItem;
menu: MenuStateReturn;
@@ -1,43 +0,0 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2022 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { observer } from 'mobx-react-lite';
import { MenuItem, MenuStateReturn } from 'reakit/Menu';
import styled from 'reshadow';
import { useTranslate } from '@cloudbeaver/core-localization';
import { ComponentStyle, useStyles } from '@cloudbeaver/core-theming';
import { MenuItemElement } from './MenuItemElement';
import { menuPanelStyles } from './menuPanelStyles';
interface Props extends Omit<React.ButtonHTMLAttributes<any>, 'style'> {
menu: MenuStateReturn; // from reakit useMenuState
style?: ComponentStyle;
}
export const MenuEmptyItem = observer<Props>(function MenuEmptyItem({
menu, style,
}) {
const styles = useStyles(menuPanelStyles, style);
const translate = useTranslate();
const label = translate('core_ui_empty');
return styled(styles)(
<MenuItem
{...menu}
id='empty'
aria-label={label}
disabled
>
<MenuItemElement label={label} style={style} />
</MenuItem>
);
});
@@ -1,60 +0,0 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2022 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { observer } from 'mobx-react-lite';
import styled from 'reshadow';
import { Icon, IconOrImage, Loader, useStateDelay } from '@cloudbeaver/core-blocks';
import { useTranslate } from '@cloudbeaver/core-localization';
import { useStyles, ComponentStyle } from '@cloudbeaver/core-theming';
import { menuPanelStyles } from './menuPanelStyles';
interface IMenuItemElementProps {
label: string;
tooltip?: string;
binding?: string;
icon?: string | React.ReactNode;
menu?: boolean;
loading?: boolean;
style?: ComponentStyle;
}
export const MenuItemElement = observer<IMenuItemElementProps>(function MenuItemElement({
label,
tooltip,
binding,
icon,
menu,
loading = false,
style = [],
}) {
const translate = useTranslate();
const title = translate(label);
loading = useStateDelay(loading, 100);
return styled(useStyles(menuPanelStyles, style))(
<menu-panel-item title={tooltip ? translate(tooltip) : title}>
<menu-item-content>
{typeof icon === 'string' ? <IconOrImage icon={icon} /> : icon}
</menu-item-content>
<menu-item-text title={title}>
{title}
</menu-item-text>
<menu-item-binding title={binding}>
{binding}
</menu-item-binding>
<menu-item-content>
{loading && <Loader small fullSize />}
{menu && !loading && <Icon name="arrow" viewBox="0 0 16 16" />}
</menu-item-content>
</menu-panel-item>
);
});
@@ -11,13 +11,11 @@ import React, { useCallback } from 'react';
import { MenuItem, MenuSeparator, MenuStateReturn } from 'reakit/Menu';
import styled, { use } from 'reshadow';
import { MenuItemElement, menuPanelStyles } from '@cloudbeaver/core-blocks';
import { ComponentStyle, joinStyles, useStyles } from '@cloudbeaver/core-theming';
import { IMenuItem, IMenuData, MenuSubMenuItem, MenuSeparatorItem, MenuActionItem, MenuBaseItem } from '@cloudbeaver/core-view';
import { MenuActionElement } from './MenuActionElement';
import { MenuItemElement } from './MenuItemElement';
import { MenuPanel } from './MenuPanel';
import { menuPanelStyles } from './menuPanelStyles';
import { SubMenuElement } from './SubMenuElement';
@@ -25,12 +23,13 @@ export interface IMenuItemRendererProps extends Omit<React.ButtonHTMLAttributes<
item: IMenuItem;
menuData: IMenuData;
menu: MenuStateReturn; // from reakit useMenuState
rtl?: boolean;
onItemClose?: () => void;
style?: ComponentStyle;
}
export const MenuItemRenderer = observer<IMenuItemRendererProps>(function MenuItemRenderer({
item, menuData, menu, onItemClose, style,
item, rtl, menuData, menu, onItemClose, style,
}) {
const styles = useStyles(menuPanelStyles, style);
const onClick = useCallback(() => {
@@ -49,7 +48,7 @@ export const MenuItemRenderer = observer<IMenuItemRendererProps>(function MenuIt
id={item.id}
aria-label={item.menu.label}
itemRenderer={MenuItemRenderer}
menuPanel={MenuPanel}
rtl={rtl}
menuData={menuData}
subMenu={item}
style={style}
@@ -1,65 +0,0 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2022 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { observer } from 'mobx-react-lite';
import { Menu, MenuStateReturn } from 'reakit/Menu';
import styled, { use } from 'reshadow';
import { getComputed } from '@cloudbeaver/core-blocks';
import { ComponentStyle, useStyles } from '@cloudbeaver/core-theming';
import { IMenuData, IMenuItem, MenuActionItem } from '@cloudbeaver/core-view';
import { MenuEmptyItem } from './MenuEmptyItem';
import { menuPanelStyles } from './menuPanelStyles';
export interface IMenuPanelProps {
menuData: IMenuData;
menu: MenuStateReturn; // from reakit useMenuState
children: (item: IMenuItem) => React.ReactNode;
panelAvailable?: boolean;
rtl?: boolean;
style?: ComponentStyle;
}
export const MenuPanel = observer<IMenuPanelProps>(function MenuPanel({
menuData,
menu,
panelAvailable = true,
rtl,
children,
style,
}) {
const styles = useStyles(menuPanelStyles, style);
const visible = menu.visible;
if (!visible) {
return null;
}
let items: IMenuItem[] = [];
if (panelAvailable) {
items = menuData.items;
}
const hasBindings = getComputed(() => items.some(
item => item instanceof MenuActionItem && item.action.binding !== null
));
return styled(styles)(
<Menu {...menu} aria-label={menuData.menu.label} visible={panelAvailable}>
<menu-box dir={rtl ? 'rtl' : undefined} {...use({ hasBindings })}>
{items.length === 0 && (
<MenuEmptyItem menu={menu} style={style} />
)}
{items.map(item => children(item))}
</menu-box>
</Menu>
);
});
@@ -7,25 +7,21 @@
*/
import { observer } from 'mobx-react-lite';
import { useCallback, useEffect } from 'react';
import { MenuButton, useMenuState } from 'reakit/Menu';
import { useRef } from 'react';
import styled from 'reshadow';
import { getComputed } from '@cloudbeaver/core-blocks';
import { getComputed, IMenuState, Menu, MenuItemElement, menuPanelStyles, useObjectRef } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { ComponentStyle, useStyles } from '@cloudbeaver/core-theming';
import { DATA_CONTEXT_MENU_NESTED, IMenuData, IMenuSubMenuItem, MenuService, useMenu } from '@cloudbeaver/core-view';
import { DATA_CONTEXT_MENU_NESTED, IMenuData, IMenuSubMenuItem, MenuActionItem, MenuService, useMenu } from '@cloudbeaver/core-view';
import { MenuItemElement } from './MenuItemElement';
import type { IMenuItemRendererProps } from './MenuItemRenderer';
import type { IMenuPanelProps } from './MenuPanel';
import { menuPanelStyles } from './menuPanelStyles';
interface ISubMenuElementProps extends Omit<React.ButtonHTMLAttributes<any>, 'style'> {
menuData: IMenuData;
subMenu: IMenuSubMenuItem;
itemRenderer: React.FC<IMenuItemRendererProps>;
menuPanel: React.FC<IMenuPanelProps>;
rtl?: boolean;
onItemClose?: () => void;
style?: ComponentStyle;
}
@@ -35,15 +31,15 @@ export const SubMenuElement = observer<ISubMenuElementProps, HTMLButtonElement>(
menuData,
subMenu,
itemRenderer,
menuPanel,
style,
rtl,
onItemClose,
...rest
},
ref
) {
const menuService = useService(MenuService);
const menu = useMenuState();
const menu = useRef<IMenuState>();
const styles = useStyles(menuPanelStyles, style);
const subMenuData = useMenu({ menu: subMenu.menu, context: menuData.context });
subMenuData.context.set(DATA_CONTEXT_MENU_NESTED, true);
@@ -51,17 +47,23 @@ export const SubMenuElement = observer<ISubMenuElementProps, HTMLButtonElement>(
const handler = menuService.getHandler(subMenuData.context);
const hidden = getComputed(() => handler?.isHidden?.(subMenuData.context));
const handleItemClose = useCallback(() => {
menu.hide();
onItemClose?.();
}, [menu, onItemClose]);
useEffect(() => {
if (menu.visible) {
subMenu.events?.onOpen?.();
handler?.handler?.(subMenuData.context);
}
}, [menu.visible]);
const handlers = useObjectRef(() => ({
handleItemClose() {
menu.current?.hide();
this.onItemClose?.();
},
hasBindings() {
return this.subMenuData.items.some(
item => item instanceof MenuActionItem && item.action.binding !== null
);
},
handleVisibleSwitch(visible: boolean) {
if (visible) {
this.subMenu.events?.onOpen?.();
this.handler?.handler?.(this.subMenuData.context);
}
},
}), { subMenuData, menuData, handler, subMenu, onItemClose }, ['handleItemClose', 'hasBindings', 'handleVisibleSwitch']);
if (hidden) {
return null;
@@ -69,40 +71,39 @@ export const SubMenuElement = observer<ISubMenuElementProps, HTMLButtonElement>(
const loading = getComputed(() => handler?.isLoading?.(subMenuData.context));
const disabled = getComputed(() => handler?.isDisabled?.(subMenuData.context));
const MenuPanel = menuPanel;
const MenuItemRenderer = itemRenderer;
return styled(styles)(
<>
<MenuButton ref={ref} {...menu} {...rest} disabled={disabled}>
<box>
<MenuItemElement
label={subMenu.menu.label}
icon={subMenu.menu.icon}
tooltip={subMenu.menu.tooltip}
loading={loading}
style={style}
menu
/>
</box>
</MenuButton>
<MenuPanel
menuData={subMenuData}
menu={menu}
<Menu
ref={ref}
menuRef={menu}
label={subMenuData.menu.label}
items={() => subMenuData.items.map(item => menu.current && (
<MenuItemRenderer
key={item.id}
item={item}
menuData={subMenuData}
menu={menu.current}
style={style}
onItemClose={handlers.handleItemClose}
/>
))}
rtl={rtl}
style={style}
panelAvailable={subMenuData.available && !loading}
disabled={disabled}
getHasBindings={handlers.hasBindings}
onVisibleSwitch={handlers.handleVisibleSwitch}
{...rest}
>
<MenuItemElement
label={subMenu.menu.label}
icon={subMenu.menu.icon}
tooltip={subMenu.menu.tooltip}
loading={loading}
style={style}
panelAvailable={subMenuData.available && !loading}
>
{item => (
<MenuItemRenderer
key={item.id}
item={item}
menuData={menuData}
menu={menu}
style={style}
onItemClose={handleItemClose}
/>
)}
</MenuPanel>
</>
menu
/>
</Menu>
);
}, { forwardRef: true });
@@ -1,139 +0,0 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2022 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { css } from 'reshadow';
export const menuPanelStyles = css`
menu-box {
composes: theme-background-surface theme-text-on-surface from global;
}
MenuItem,
MenuItemCheckbox,
MenuItemRadio,
MenuButton,
MenuItemElement {
composes: theme-ripple from global;
}
menu-panel-item, MenuSeparator {
composes: theme-border-color-background from global;
}
MenuButton {
background: none;
border: none;
outline: none !important;
color: inherit;
cursor: pointer;
& box {
display: flex;
align-items: center;
flex: 1;
height: inherit;
}
}
Menu {
outline: none;
z-index: 999;
}
menu-box {
composes: theme-typography--body2 theme-elevation-z5 from global;
min-width: 140px;
padding: 12px 0;
& menu-box {
margin-top: -12px;
}
}
menu-box {
display: flex;
flex-direction: column;
&:not([|hasBindings]) menu-item-binding {
width: 0;
}
}
MenuItem, MenuItemCheckbox, MenuItemRadio {
display: flex;
align-items: center;
border: none;
padding: 0;
background: none;
text-align: left;
outline: none;
color: inherit;
cursor: pointer;
white-space: nowrap;
&[use|hidden] {
display: none;
}
&:hover, &:global([aria-expanded="true"]) {
& Icon {
opacity: 1;
}
}
}
MenuSeparator {
flex: 1;
width: 100%;
display: flex;
margin: 4px 0;
border: none !important;
border-bottom: 1px solid !important;
}
menu-item-binding {
composes: theme-typography--caption from global;
}
menu-panel-item {
flex: 1;
display: flex;
position: relative;
align-items: center;
height: 30px;
padding: 0 4px;
& menu-item-text, & menu-item-binding {
display: block;
padding: 0 4px;
}
& menu-item-binding {
flex: 1;
text-align: right;
padding-left: 32px;
width: 80px;
overflow: hidden;
text-overflow: ellipsis;
}
& menu-item-content {
width: 24px;
height: 24px;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
& Icon {
width: 16px;
height: 16px;
opacity: 0.5;
transform: rotate(-90deg);
}
& IconOrImage {
width: 16px;
height: 16px;
object-fit: contain;
}
& Loader {
width: 16px;
}
}
`;
-1
View File
@@ -1,7 +1,6 @@
export * from './Clipboard/ClipboardBootstrap';
export * from './Clipboard/ClipboardService';
export * from './ContextMenu/MenuItemElement';
export * from './ContextMenu/ContextMenu';
export * from './ContextMenu/MenuBar/MenuBar';
export * from './ContextMenu/MenuBar/MENU_BAR_DEFAULT_STYLES';