diff --git a/.changeset/fast-rocks-tell.md b/.changeset/fast-rocks-tell.md new file mode 100644 index 000000000..0f15ee9aa --- /dev/null +++ b/.changeset/fast-rocks-tell.md @@ -0,0 +1,5 @@ +--- +"@hi-ui/tree": minor +--- + +feat: 增加 actionRender API diff --git a/.changeset/rude-lamps-bathe.md b/.changeset/rude-lamps-bathe.md new file mode 100644 index 000000000..7ed4624d0 --- /dev/null +++ b/.changeset/rude-lamps-bathe.md @@ -0,0 +1,5 @@ +--- +"@hi-ui/hiui": patch +--- + +Tree feat: 增加 actionRender API diff --git a/packages/ui/tree/src/styles/editable-tree.scss b/packages/ui/tree/src/styles/editable-tree.scss index cedee49ad..ff35d626c 100644 --- a/packages/ui/tree/src/styles/editable-tree.scss +++ b/packages/ui/tree/src/styles/editable-tree.scss @@ -21,26 +21,32 @@ $prefix: '#{$component-prefix}-tree' !default; width: 38px; } - .#{$prefix}-action-btn { - position: relative; - color: use-color('gray', 800); + .#{$prefix}-action-wrap { transition: opacity 0.3s; opacity: 0; visibility: hidden; - &:hover { - color: use-color-mode('primary'); - } - &--visible { visibility: visible; opacity: 1; + + .#{$prefix}-action-btn { + color: use-color-mode('primary'); + } + } + } + + .#{$prefix}-action-btn { + position: relative; + color: use-color('gray', 800); + + &:hover { color: use-color-mode('primary'); } } .#{$prefix}-node:hover { - .#{$prefix}-action-btn { + .#{$prefix}-action-wrap { visibility: visible; opacity: 1; } diff --git a/packages/ui/tree/src/types.ts b/packages/ui/tree/src/types.ts index 3fee621bf..5ae85f99e 100644 --- a/packages/ui/tree/src/types.ts +++ b/packages/ui/tree/src/types.ts @@ -145,6 +145,10 @@ export type TreeEditActions = { * 执行删除节点操作 */ deleteNode: () => void + /** + * 执行打开菜单操作 + */ + openMenu: () => void /** * 执行关闭菜单操作 */ diff --git a/packages/ui/tree/src/use-tree-action.tsx b/packages/ui/tree/src/use-tree-action.tsx index 929038bc0..e66d21ed8 100644 --- a/packages/ui/tree/src/use-tree-action.tsx +++ b/packages/ui/tree/src/use-tree-action.tsx @@ -1,6 +1,14 @@ import React, { useState, useCallback, useMemo, useRef, forwardRef } from 'react' import { __DEV__ } from '@hi-ui/env' import { cx } from '@hi-ui/classname' +import { useOutsideClick } from '@hi-ui/use-outside-click' +import { useMergeRefs } from '@hi-ui/use-merge-refs' +import { useToggle, UseToggleAction } from '@hi-ui/use-toggle' +import { useLatestRef } from '@hi-ui/use-latest' +import { isArrayNonEmpty, isFunction } from '@hi-ui/type-assertion' +import Input from '@hi-ui/input' +import Popper from '@hi-ui/popper' +import { CheckOutlined, CloseOutlined } from '@hi-ui/icons' import { TreeProps, Tree, treePrefix } from './Tree' import { FlattedTreeNodeData, @@ -9,20 +17,12 @@ import { TreeDataStatus, TreeMenuActionOption, TreeNodeEventData, + TreeEditActions, } from './types' import { useEdit, useCache, useExpandProps } from './hooks' import { flattenTreeData } from './utils' -import Input from '@hi-ui/input' -import { useOutsideClick } from '@hi-ui/use-outside-click' -import { useMergeRefs } from '@hi-ui/use-merge-refs' -import { useToggle, UseToggleAction } from '@hi-ui/use-toggle' -import { useLatestRef } from '@hi-ui/use-latest' -import Popper from '@hi-ui/popper' -import { CheckOutlined, CloseOutlined } from '@hi-ui/icons' -// import Button from '@hi-ui/button' import { IconButton } from './IconButton' import { defaultActionIcon } from './icons' -import { isArrayNonEmpty, isFunction } from '@hi-ui/type-assertion' import './styles/editable-tree.scss' @@ -73,6 +73,7 @@ export const useTreeEditProps = ( menuOptions, editPlaceholder: placeholder, fieldNames, + actionRender, ...nativeTreeProps } = props const [treeData, setTreeData] = useCache(data) @@ -118,6 +119,7 @@ export const useTreeEditProps = ( expandedIds={expandedIds} focusTree={focusTree} onExpand={tryToggleExpandedIds} + actionRender={actionRender} /> ) } @@ -185,10 +187,14 @@ export interface EditableTreeProps extends TreeProps { * 输入框占位符 */ editPlaceholder?: string + /** + * 自定义可编辑树操作项 + */ + actionRender?: (node: FlattedTreeNodeData, editActions: TreeEditActions) => React.ReactNode } const EditableTreeNodeTitle = (props: EditableTreeNodeTitleProps) => { - const { prefixCls, node, title } = props + const { prefixCls, node, title, actionRender } = props // 如果是添加节点,则进入节点编辑临时态 const [editing, editingAction] = useToggle(() => node.raw.type === TreeNodeType.ADD || false) @@ -200,7 +206,7 @@ const EditableTreeNodeTitle = (props: EditableTreeNodeTitleProps) => { return (
{title || node.title} - +
) } @@ -219,6 +225,7 @@ interface EditableTreeNodeTitleProps { placeholder?: string menuOptions?: TreeMenuActionOption[] | ((node: FlattedTreeNodeData) => TreeMenuActionOption[]) focusTree: () => void + actionRender?: (node: FlattedTreeNodeData, editActions: TreeEditActions) => React.ReactNode | null } const EditableNodeMenu = (props: EditableNodeMenuProps) => { @@ -232,6 +239,7 @@ const EditableNodeMenu = (props: EditableNodeMenuProps) => { expandedIds, onExpand, menuOptions: menuOptionsProp, + actionRender, } = props const [menuVisible, menuVisibleAction] = useToggle(false) @@ -269,6 +277,9 @@ const EditableNodeMenu = (props: EditableNodeMenuProps) => { menuVisibleAction.off() addSiblingNode(node) }, + openMenu: () => { + menuVisibleAction.on() + }, closeMenu: () => { menuVisibleAction.off() }, @@ -297,44 +308,54 @@ const EditableNodeMenu = (props: EditableNodeMenuProps) => { if (!isArrayNonEmpty(menuOptions)) return null return ( - <> - { - // 阻止冒泡,避免触发节点选中 - evt.stopPropagation() - menuVisibleAction.not() - }} - /> - -
    - {menuOptions.map((option, idx) => ( -
  • { - // 阻止冒泡,避免触发节点选中 - evt.stopPropagation() - handleMenuClick(node, option) - }} - > - {option.title} -
  • - ))} -
-
- +
{ + // 阻止冒泡,避免触发节点选中 + evt.stopPropagation() + }} + > + {isFunction(actionRender) ? ( + actionRender(node, menuActionsRef.current) + ) : ( + <> + { + menuVisibleAction.not() + }} + /> + +
    + {menuOptions.map((option, idx) => ( +
  • { + // 阻止冒泡,避免触发节点选中 + evt.stopPropagation() + handleMenuClick(node, option) + }} + > + {option.title} +
  • + ))} +
+
+ + )} +
) } diff --git a/packages/ui/tree/stories/action-render.stories.tsx b/packages/ui/tree/stories/action-render.stories.tsx new file mode 100644 index 000000000..308a52fb8 --- /dev/null +++ b/packages/ui/tree/stories/action-render.stories.tsx @@ -0,0 +1,107 @@ +import React from 'react' +import Tree, { useTreeAction } from '../src' +import Space from '@hi-ui/space' +import PopConfirm from '@hi-ui/pop-confirm' +import { PlusOutlined, DuplicateOutlined, EditOutlined, DeleteOutlined } from '@hi-ui/icons' + +/** + * @title 自定义可编辑树操作项 + * @desc 用于操作菜单放在外面的场景 + */ +export const ActionRender = () => { + const ActionTree = useTreeAction(Tree) + + return ( + <> +

ActionRender for Tree

+
+ { + console.log('node', node) + + const { id } = node + + return id === 11 ? ( + + editActions.addChildNode()} /> + editActions.addSiblingNode()} /> + editActions.editNode()} /> + + editActions.openMenu()} /> + + + ) : null + }} + menuOptions={[ + { + type: 'addChildNode', + title: '新建子节点', + }, + { + type: 'addSiblingNode', + title: '新建兄弟节点', + }, + { + // type: 'deleteNode', + title: '删除当前菜单', + onClick(node, action) { + action.closeMenu() + + Modal.confirm({ + title: '提示', + content: '确定删除吗?', + onConfirm: () => { + action.deleteNode() + }, + }) + }, + }, + { + type: 'editNode', + title: '编辑当前菜单', + }, + { + title: 'Hello,自定义的菜单', + onClick(node, action) { + console.log(node) + action.closeMenu() + }, + }, + ]} + data={[ + { + id: 1, + title: '小米', + children: [ + { + id: 2, + title: '研发', + children: [ + { id: 3, title: '后端' }, + { id: 4, title: '运维' }, + { id: 5, title: '前端' }, + ], + }, + { id: 6, title: '产品' }, + ], + }, + { + id: 11, + title: '大米', + children: [ + { id: 22, title: '可视化' }, + { id: 66, title: 'HiUI' }, + ], + }, + ]} + /> +
+ + ) +} diff --git a/packages/ui/tree/stories/editable.stories.tsx b/packages/ui/tree/stories/editable.stories.tsx index 67068c51d..c5681aa3f 100644 --- a/packages/ui/tree/stories/editable.stories.tsx +++ b/packages/ui/tree/stories/editable.stories.tsx @@ -14,6 +14,7 @@ export const Editable = () => {

Editable for Tree