Skip to content

Commit

Permalink
feat: 新增 dropdown-contextmenus 组件 (#11511)
Browse files Browse the repository at this point in the history
  • Loading branch information
2betop authored Jan 16, 2025
1 parent b5b9b8a commit e62d9e2
Show file tree
Hide file tree
Showing 7 changed files with 231 additions and 7 deletions.
8 changes: 7 additions & 1 deletion packages/amis-core/src/components/PopOver.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,12 @@ export class PopOver extends React.PureComponent<PopOverProps, PopOverState> {
});
}

@autobind
handleOverlayClick(e: React.MouseEvent<HTMLDivElement>) {
e.preventDefault();
this.props.onHide?.();
}

render() {
const {
placement,
Expand Down Expand Up @@ -233,7 +239,7 @@ export class PopOver extends React.PureComponent<PopOverProps, PopOverState> {
{overlay ? (
<div
className={`${ns}PopOver-overlay`}
onClick={onHide}
onClick={this.handleOverlayClick}
{...testIdBuilder?.getChild('overlay').getTestId()}
/>
) : null}
Expand Down
6 changes: 6 additions & 0 deletions packages/amis-ui/scss/components/_context-menu.scss
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,12 @@
&.has-child:hover > a::after {
border-color: transparent transparent transparent var(--menu-active-color);
}

&.is-danger {
> a {
color: var(--danger-color);
}
}
}

&-itemIcon {
Expand Down
27 changes: 27 additions & 0 deletions packages/amis-ui/scss/components/_dropdown-context-menus.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
.#{$ns}DropdownContextMenus {
width: px2rem(20px);
height: px2rem(20px);
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
border-radius: var(--borders-radius-4);
background-color: var(--button-light-default-bg-color);
color: var(--icon-color);

&:hover {
background-color: var(--button-light-hover-bg-color);
color: var(--icon-onHover-color);
}

&.is-disabled {
color: var(--icon-onDisabled-color);
}

> svg {
fill: currentColor;
width: px2rem(12px);
height: px2rem(12px);
transform: rotate(90deg);
}
}
1 change: 1 addition & 0 deletions packages/amis-ui/scss/themes/_common.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
@import '../components/badge';
@import '../components/modal';
@import '../components/drawer';
@import '../components/dropdown-context-menus';
@import '../components/tooltip';
@import '../components/tpl';
@import '../components/popover';
Expand Down
174 changes: 174 additions & 0 deletions packages/amis-ui/src/components/DropdownContextMenus.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
/**
* @file DropdownContextMenus
* @desc 下拉菜单组件,用于展示上下文菜单
*/

import React from 'react';
import {themeable} from 'amis-core';
import {Icon} from './icons';
import {ThemeProps} from 'amis-core';
import PopOverContainer from './PopOverContainer';

/**
* 菜单项配置
*/
export interface ContextMenu<T = any> {
/** 菜单项 ID */
id?: string;
/** 菜单项文本 */
label: string;
/** 菜单项图标 */
icon?: string;
/** 菜单项等级 */
level?: 'normal' | 'danger';

className?: string;
disabled?: boolean;
/** 点击菜单项的回调 */
onClick?: (item: T) => void;
}

/**
* 组件属性定义
*/
interface DropdownContextMenusProps<T = Record<string, any>>
extends ThemeProps {
/** 是否禁用 */
disabled?: boolean;
/** 上下文数据 */
context: T;

/** 获取目标元素的方法 */
getTargetElement?: (dom: HTMLElement) => HTMLElement;

/** 上下文菜单配置,可以是数组或返回数组的函数 */
contextMenus: ContextMenu<T>[] | ((item: T) => ContextMenu<T>[]);
/** 上下文菜单点击回调 */
onContextMenu?: (item: T, menu: ContextMenu<T>) => void;

/** 弹出框容器 */
popOverContainer?: React.ReactNode | (() => React.ReactNode);

/** 自定义样式 */
style?: React.CSSProperties;
/** 自定义类名 */
className?: string;
}

/**
* 下拉菜单组件
* 用于展示上下文菜单的弹出层组件
*/
export function DropdownContextMenus<T = Record<string, any>>({
context,
contextMenus,
classnames: cx,
onContextMenu,
popOverContainer,
getTargetElement,
style,
className
}: DropdownContextMenusProps<T>) {
const [menus, setMenus] = React.useState<ContextMenu<T>[]>([]);
const domRef = React.useRef<HTMLElement>();

/** 处理菜单项点击事件 */
const handleItemClick = React.useCallback(
(e: React.MouseEvent) => {
e.preventDefault();
const index = e.currentTarget.getAttribute('data-index') || '';
const menu = menus[parseInt(index, 10)];
if (menu) {
menu.onClick?.(context);
onContextMenu?.(context, menu);
}
},
[menus, onContextMenu, context]
);

/** 渲染弹出层内容 */
const popOverRender = React.useMemo(() => {
return ({onClose}: any) => {
return (
<ul className={cx('ContextMenu-list')} onClick={onClose}>
{Array.isArray(menus) && menus.length ? (
menus.map((menu, index) => (
<li
key={`${index}`}
className={cx('ContextMenu-item', menu.className, {
[`is-${menu.level}`]: menu.level && menu.level !== 'normal',
'is-disabled': menu.disabled
})}
>
<a data-index={index} onClick={handleItemClick}>
{menu.icon ? (
<span className={cx('ContextMenu-itemIcon', menu.icon)} />
) : null}
{menu.label}
</a>
</li>
))
) : (
<li
onClick={handleItemClick}
className={cx('ContextMenu-item is-disabled is-placeholder')}
>
<a></a>
</li>
)}
</ul>
);
};
}, [menus]);

/** 处理弹出层打开事件 */
const handleOpen = React.useCallback(async () => {
if (typeof contextMenus === 'function') {
setMenus(await contextMenus(context));
} else {
setMenus(contextMenus);
}
}, [context, contextMenus]);

/** 获取弹出层容器 */
const getContainer = React.useCallback(() => {
return typeof popOverContainer === 'function'
? popOverContainer()
: popOverContainer || domRef.current;
}, [popOverContainer]);

return (
<PopOverContainer
popOverRender={popOverRender}
popOverContainer={getContainer}
onOpen={handleOpen}
>
{({onClick, ref: targetRef, isOpened}) => {
const filterRef = (ref: any) => {
domRef.current = ref;
if (ref && getTargetElement) {
ref = getTargetElement(ref);
}
targetRef(ref);
};

return (
<a
ref={filterRef}
className={cx(
'DropdownContextMenus',
className,
isOpened ? 'is-open' : ''
)}
onClick={onClick}
style={style}
>
<Icon icon="ellipsis-v" />
</a>
);
}}
</PopOverContainer>
);
}

export default themeable(DropdownContextMenus);
17 changes: 13 additions & 4 deletions packages/amis-ui/src/components/PopOverContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export interface PopOverContainerProps {
ref: any;
}) => JSX.Element;
disabled?: boolean;
/** 弹出层打开时触发的事件 */
onOpen?: () => void;
popOverRender: (props: {onClose: () => void}) => JSX.Element;
popOverContainer?: any;
popOverClassName?: string;
Expand Down Expand Up @@ -63,11 +65,18 @@ export class PopOverContainer extends React.Component<
}

@autobind
handleClick() {
handleClick(e?: React.MouseEvent) {
e?.preventDefault();

this.props.disabled ||
this.setState({
isOpened: true
});
this.setState(
{
isOpened: true
},
() => {
this.props.onOpen?.();
}
);
}

@autobind
Expand Down
5 changes: 3 additions & 2 deletions packages/amis-ui/src/components/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ import VerificationCode from './VerificationCode';
import Shape from './Shape';
import type {IShapeType} from './Shape';
import MobileDevTool from './MobileDevTool';

import DropdownContextMenus from './DropdownContextMenus';
export {
NotFound,
Alert as AlertComponent,
Expand Down Expand Up @@ -287,5 +287,6 @@ export {
VerificationCode,
Shape,
IShapeType,
MobileDevTool
MobileDevTool,
DropdownContextMenus
};

0 comments on commit e62d9e2

Please sign in to comment.