ant-design/components/tree/Tree.tsx
ice 5a9a2d95ec
feat(tree): add switcherLoadingIcon prop (#49716)
* feat(tree): add switcherLoadingIcon prop

* refactor: lint

* Update components/tree/index.en-US.md

add version desc

Co-authored-by: lijianan <574980606@qq.com>
Signed-off-by: ice <49827327+coding-ice@users.noreply.github.com>

* docs: add version desc

---------

Signed-off-by: ice <49827327+coding-ice@users.noreply.github.com>
Co-authored-by: lijianan <574980606@qq.com>
2024-07-22 19:49:29 +08:00

275 lines
7.5 KiB
TypeScript

import type { Component } from 'react';
import React from 'react';
import HolderOutlined from '@ant-design/icons/HolderOutlined';
import classNames from 'classnames';
import type { CSSMotionProps } from 'rc-motion';
import type { BasicDataNode, TreeProps as RcTreeProps } from 'rc-tree';
import RcTree from 'rc-tree';
import type { DataNode, Key } from 'rc-tree/lib/interface';
import initCollapseMotion from '../_util/motion';
import { ConfigContext } from '../config-provider';
import { useToken } from '../theme/internal';
import useStyle from './style';
import dropIndicatorRender from './utils/dropIndicator';
import SwitcherIconCom from './utils/iconUtil';
export type SwitcherIcon = React.ReactNode | ((props: AntTreeNodeProps) => React.ReactNode);
export type TreeLeafIcon = React.ReactNode | ((props: AntTreeNodeProps) => React.ReactNode);
type TreeIcon = React.ReactNode | ((props: AntdTreeNodeAttribute) => React.ReactNode);
export interface AntdTreeNodeAttribute {
eventKey: string;
prefixCls: string;
className: string;
expanded: boolean;
selected: boolean;
checked: boolean;
halfChecked: boolean;
children: React.ReactNode;
title: React.ReactNode;
pos: string;
dragOver: boolean;
dragOverGapTop: boolean;
dragOverGapBottom: boolean;
isLeaf: boolean;
selectable: boolean;
disabled: boolean;
disableCheckbox: boolean;
}
export interface AntTreeNodeProps {
className?: string;
checkable?: boolean;
disabled?: boolean;
disableCheckbox?: boolean;
title?: React.ReactNode | ((data: DataNode) => React.ReactNode);
key?: Key;
eventKey?: Key;
isLeaf?: boolean;
checked?: boolean;
expanded?: boolean;
loading?: boolean;
selected?: boolean;
selectable?: boolean;
icon?: TreeIcon;
children?: React.ReactNode;
[customProp: string]: any;
}
export interface AntTreeNode extends Component<AntTreeNodeProps> {}
export interface AntTreeNodeBaseEvent {
node: AntTreeNode;
nativeEvent: MouseEvent;
}
export interface AntTreeNodeCheckedEvent extends AntTreeNodeBaseEvent {
event: 'check';
checked?: boolean;
checkedNodes?: AntTreeNode[];
}
export interface AntTreeNodeSelectedEvent extends AntTreeNodeBaseEvent {
event: 'select';
selected?: boolean;
selectedNodes?: DataNode[];
}
export interface AntTreeNodeExpandedEvent extends AntTreeNodeBaseEvent {
expanded?: boolean;
}
export interface AntTreeNodeMouseEvent {
node: AntTreeNode;
event: React.DragEvent<HTMLElement>;
}
export interface AntTreeNodeDragEnterEvent extends AntTreeNodeMouseEvent {
expandedKeys: Key[];
}
export interface AntTreeNodeDropEvent {
node: AntTreeNode;
dragNode: AntTreeNode;
dragNodesKeys: Key[];
dropPosition: number;
dropToGap?: boolean;
event: React.MouseEvent<HTMLElement>;
}
// [Legacy] Compatible for v3
export type TreeNodeNormal = DataNode;
type DraggableFn = (node: DataNode) => boolean;
interface DraggableConfig {
icon?: React.ReactNode;
nodeDraggable?: DraggableFn;
}
export interface TreeProps<T extends BasicDataNode = DataNode>
extends Omit<
RcTreeProps<T>,
'prefixCls' | 'showLine' | 'direction' | 'draggable' | 'icon' | 'switcherIcon'
> {
showLine?: boolean | { showLeafIcon: boolean | TreeLeafIcon };
className?: string;
/** Whether to support multiple selection */
multiple?: boolean;
/** Whether to automatically expand the parent node */
autoExpandParent?: boolean;
/** Node selection in Checkable state is fully controlled (the selected state of parent and child nodes is no longer associated) */
checkStrictly?: boolean;
/** Whether to support selection */
checkable?: boolean;
/** whether to disable the tree */
disabled?: boolean;
/** Expand all tree nodes by default */
defaultExpandAll?: boolean;
/** Expand the corresponding tree node by default */
defaultExpandParent?: boolean;
/** Expand the specified tree node by default */
defaultExpandedKeys?: Key[];
/** (Controlled) Expand the specified tree node */
expandedKeys?: Key[];
/** (Controlled) Tree node with checked checkbox */
checkedKeys?: Key[] | { checked: Key[]; halfChecked: Key[] };
/** Tree node with checkbox checked by default */
defaultCheckedKeys?: Key[];
/** (Controlled) Set the selected tree node */
selectedKeys?: Key[];
/** Tree node selected by default */
defaultSelectedKeys?: Key[];
selectable?: boolean;
/** Click on the tree node to trigger */
filterAntTreeNode?: (node: AntTreeNode) => boolean;
loadedKeys?: Key[];
/** Set the node to be draggable (IE>8) */
draggable?: DraggableFn | boolean | DraggableConfig;
style?: React.CSSProperties;
showIcon?: boolean;
icon?: TreeIcon;
switcherIcon?: SwitcherIcon;
switcherLoadingIcon?: React.ReactNode;
prefixCls?: string;
children?: React.ReactNode;
blockNode?: boolean;
}
const Tree = React.forwardRef<RcTree, TreeProps>((props, ref) => {
const { getPrefixCls, direction, virtual, tree } = React.useContext(ConfigContext);
const {
prefixCls: customizePrefixCls,
className,
showIcon = false,
showLine,
switcherIcon,
switcherLoadingIcon,
blockNode = false,
children,
checkable = false,
selectable = true,
draggable,
motion: customMotion,
style,
} = props;
const prefixCls = getPrefixCls('tree', customizePrefixCls);
const rootPrefixCls = getPrefixCls();
const motion: CSSMotionProps = customMotion ?? {
...initCollapseMotion(rootPrefixCls),
motionAppear: false,
};
const newProps = {
...props,
checkable,
selectable,
showIcon,
motion,
blockNode,
showLine: Boolean(showLine),
dropIndicatorRender,
};
const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls);
const [, token] = useToken();
const itemHeight = token.paddingXS / 2 + (token.Tree?.titleHeight || token.controlHeightSM);
const draggableConfig = React.useMemo(() => {
if (!draggable) {
return false;
}
let mergedDraggable: DraggableConfig = {};
switch (typeof draggable) {
case 'function':
mergedDraggable.nodeDraggable = draggable;
break;
case 'object':
mergedDraggable = { ...draggable };
break;
default:
break;
// Do nothing
}
if (mergedDraggable.icon !== false) {
mergedDraggable.icon = mergedDraggable.icon || <HolderOutlined />;
}
return mergedDraggable;
}, [draggable]);
const renderSwitcherIcon = (nodeProps: AntTreeNodeProps) => (
<SwitcherIconCom
prefixCls={prefixCls}
switcherIcon={switcherIcon}
switcherLoadingIcon={switcherLoadingIcon}
treeNodeProps={nodeProps}
showLine={showLine}
/>
);
return wrapCSSVar(
// @ts-ignore
<RcTree
itemHeight={itemHeight}
ref={ref}
virtual={virtual}
{...newProps}
// newProps may contain style so declare style below it
style={{ ...tree?.style, ...style }}
prefixCls={prefixCls}
className={classNames(
{
[`${prefixCls}-icon-hide`]: !showIcon,
[`${prefixCls}-block-node`]: blockNode,
[`${prefixCls}-unselectable`]: !selectable,
[`${prefixCls}-rtl`]: direction === 'rtl',
},
tree?.className,
className,
hashId,
cssVarCls,
)}
direction={direction}
checkable={checkable ? <span className={`${prefixCls}-checkbox-inner`} /> : checkable}
selectable={selectable}
switcherIcon={renderSwitcherIcon}
draggable={draggableConfig}
>
{children}
</RcTree>,
);
});
if (process.env.NODE_ENV !== 'production') {
Tree.displayName = 'Tree';
}
export default Tree;