Repository: arco-design/arco-design (ts)

Issue:
Tree组件支持某个父节点可以点击，展开/收起的效果，但是不选中的效果
- [ ] I'm sure this does not appear in [the issue list of the repository](https://github.com/arco-design/arco-design/issues)

## Basic Info 

## What are the similar cases of this feature
not found

## What problem does this feature solve?
某些场景下，Tree组件需要展开父节点，但是不选中


<!-- generated by arco-issue. DO NOT REMOVE -->

Pull request:
feat: Tree supports actionOnClick
<!--
  Thanks so much for your PR and contribution.

  Before submitting, please make sure to follow the Pull Request Guidelines: https://github.com/arco-design/arco-design/blob/main/CONTRIBUTING.md
-->

<!-- Put an `x` in "[ ]" to check a box) -->

## Types of changes

<!-- What types of changes does this PR introduce -->
- [x] New feature
- [ ] Bug fix
- [ ] Documentation change
- [ ] Coding style change
- [ ] Component style change
- [ ] Refactoring
- [ ] Performance improvement
- [ ] Test cases
- [ ] Continuous integration
- [ ] Typescript definition change
- [ ] Breaking change
- [ ] Others 

## Background and context

<!-- Explain what problem does the PR solve -->
<!-- Link to related open issues if applicable -->

## Solution

<!-- Describe how the problem is fixed in detail -->

## How is the change tested?

<!-- Unit tests should be added/updated for bug fixes and new features, if applicable -->
<!-- Please describe how you tested the change. E.g. Creating/updating unit tests or attaching a screenshot of how it works with your change -->

## Changelog

| Component | Changelog(CN) | Changelog(EN) | Related issues |
| --------- | ------------- | ------------- | -------------- |
|     Tree      |    `Tree` 组件支持通过 `expandOnClick` 属性设置点击节点时展开子节点         |       The `Tree` component supports expanding child nodes when a node is clicked via the `expandOnClick` property        |          close #426      |

## Checklist:

- [ ] Test suite passes (`npm run test`)
- [ ] Provide changelog for relevant changes (e.g. bug fixes and new features) if applicable.
- [ ] Changes are submitted to the appropriate branch (e.g. features should be submitted to `feature` branch and others should be submitted to `main` branch)

## Other information

<!-- Please describe what other information that should be taken care of. E.g. describe the impact if introduce a breaking change -->


Patch:
diff --git a/components/Checkbox/__demo__/custom_render.md b/components/Checkbox/__demo__/custom_render.md
new file mode 100644
index 00000000..4ac69692
--- /dev/null
+++ b/components/Checkbox/__demo__/custom_render.md
@@ -0,0 +1,117 @@
+---
+order: 5
+title:
+  zh-CN: 自定义节点内容
+  en-US: Custom Render Checkbox
+---
+
+## zh-CN
+
+可以通过传入函数类型的 `children` 来自定义渲染节点内容。
+
+
+## en-US
+
+Render nodes can be customized by passing a function of type 'children'.
+
+```js
+import { Checkbox, Tag, Space, Typography } from '@arco-design/web-react';
+
+ReactDOM.render(
+  <div>
+    <div style={{marginBottom: 20}}>
+      <Checkbox.Group defaultValue={['Beijing']}>
+        {['Beijing', 'Shanghai', 'Guangzhou'].map((item) => {
+          return (
+            <Checkbox key={item} value={item}>
+              {({ checked }) => {
+                return <Tag key={item} color={checked ? 'arcoblue' : ''}>{item}</Tag>
+              }}
+            </Checkbox>
+          )
+        })
+        }
+      </Checkbox.Group>
+    </div>
+    <Checkbox.Group>
+      {[1, 2].map((item) => {
+        return (
+          <Checkbox key={item} value={item}>
+            {({ checked }) => {
+              return (
+                <Space
+                  align="start"
+                  className={`custom-checkbox-card ${checked ? 'custom-checkbox-card-checked' : ''}`}
+                >
+                  <div className="custom-checkbox-card-mask">
+                    <div className="custom-checkbox-card-mask-dot"></div>
+                  </div>
+                  <div>
+                    <div className="custom-checkbox-card-title">Checkbox Card {item}</div>
+                    <Typography.Text type="secondary">this is a text</Typography.Text>
+                  </div>
+                </Space>
+              );
+            }}
+          </Checkbox>
+        );
+      })}
+    </Checkbox.Group>
+  </div>,
+CONTAINER);
+```
+
+```css
+
+.custom-checkbox-card {
+  padding: 10px 16px;
+  border: 1px solid var(--color-border-2);
+  border-radius: 4px;
+  width: 250px;
+  box-sizing: border-box;
+}
+
+.custom-checkbox-card-mask {
+  height: 14px;
+  width: 14px;
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  border-radius: 2px;
+  border: 1px solid var(--color-border-2);
+  box-sizing: border-box;
+}
+
+.custom-checkbox-card-mask-dot {
+  width: 8px;
+  height: 8px;
+  border-radius: 2px;
+}
+
+.custom-checkbox-card-title {
+  color: var(--color-text-1);
+  font-size: 14px;
+  font-weight: bold;
+  margin-bottom: 8px;
+}
+
+.custom-checkbox-card:hover,
+.custom-checkbox-card-checked,
+.custom-checkbox-card:hover .custom-checkbox-card-mask,
+.custom-checkbox-card-checked  .custom-checkbox-card-mask{
+  border-color: rgb(var(--primary-6));
+}
+
+.custom-checkbox-card-checked {
+  background-color: var(--color-primary-light-1);
+}
+
+.custom-checkbox-card:hover .custom-checkbox-card-title,
+.custom-checkbox-card-checked .custom-checkbox-card-title {
+  color: rgb(var(--primary-6));
+}
+
+.custom-checkbox-card-checked .custom-checkbox-card-mask-dot {
+  background-color: rgb(var(--primary-6));
+}
+```
diff --git a/components/Checkbox/checkbox.tsx b/components/Checkbox/checkbox.tsx
index 9467d525..305c7863 100644
--- a/components/Checkbox/checkbox.tsx
+++ b/components/Checkbox/checkbox.tsx
@@ -1,4 +1,4 @@
-import React, { useContext, useCallback, useEffect } from 'react';
+import React, { useContext, useCallback, useRef, useEffect } from 'react';
 import Group, { CheckboxGroupContext } from './group';
 import cs from '../_util/classNames';
 import { ConfigContext } from '../ConfigProvider';
@@ -9,8 +9,11 @@ import Hover from '../_class/icon-hover';
 import IconCheck from './icon-check';
 import { CheckboxProps } from './interface';
 import useMergeProps from '../_util/hooks/useMergeProps';
+import { isFunction } from '../_util/is';
 
 function Checkbox<T extends React.ReactText>(baseProps: CheckboxProps<T>, ref) {
+  const inputRef = useRef<HTMLInputElement>(null);
+
   const { getPrefixCls, componentConfig } = useContext(ConfigContext);
   const props = useMergeProps<CheckboxProps>(baseProps, {}, componentConfig?.Checkbox);
 
@@ -64,27 +67,52 @@ function Checkbox<T extends React.ReactText>(baseProps: CheckboxProps<T>, ref) {
     [onGroupChange, context.isCheckboxGroup, props.onChange, props.value]
   );
 
+  const onLabelClick = React.useCallback(
+    (e) => {
+      if (isFunction(props.children)) {
+        // 避免children中含有表单元素造成label无法触发input的onchange的情况
+        e.preventDefault();
+        inputRef.current && inputRef.current.click();
+      }
+      rest.onClick && rest.onClick(e);
+    },
+    [props.children, rest.onClick]
+  );
   return (
-    <label ref={ref} {...omit(rest, ['onChange'])} className={classNames} style={style}>
+    <label
+      ref={ref}
+      {...omit(rest, ['onChange'])}
+      onClick={onLabelClick}
+      className={classNames}
+      style={style}
+    >
       <input
         value={value}
         disabled={!!disabled}
+        ref={inputRef}
         checked={!!checked}
         onChange={onChange}
         // To avoid triggering onChange twice in Select if it's used in Select option.
         onClick={(e) => e.stopPropagation()}
         type="checkbox"
       />
-      <Hover
-        prefix={prefixCls}
-        className={`${prefixCls}-mask-wrapper`}
-        disabled={checked || disabled || indeterminate}
-      >
-        <div className={`${prefixCls}-mask`}>
-          <IconCheck className={`${prefixCls}-mask-icon`} />
-        </div>
-      </Hover>
-      {children && <span className={`${prefixCls}-text`}>{children}</span>}
+
+      {isFunction(children) ? (
+        children({ checked, indeterminate })
+      ) : (
+        <>
+          <Hover
+            prefix={prefixCls}
+            className={`${prefixCls}-mask-wrapper`}
+            disabled={checked || disabled || indeterminate}
+          >
+            <div className={`${prefixCls}-mask`}>
+              <IconCheck className={`${prefixCls}-mask-icon`} />
+            </div>
+          </Hover>
+          {children && <span className={`${prefixCls}-text`}>{children}</span>}
+        </>
+      )}
     </label>
   );
 }
diff --git a/components/Checkbox/interface.ts b/components/Checkbox/interface.ts
index d23ded27..7adc48a5 100644
--- a/components/Checkbox/interface.ts
+++ b/components/Checkbox/interface.ts
@@ -5,7 +5,8 @@ import React, { CSSProperties, ReactNode } from 'react';
  * @zh `T = string | number`
  * @en `T = string | number`
  */
-export interface CheckboxProps<T extends React.ReactText = any> {
+export interface CheckboxProps<T extends React.ReactText = any>
+  extends Omit<React.HTMLAttributes<HTMLLabelElement>, 'className' | 'onChange'> {
   style?: CSSProperties;
   className?: string | string[];
   /**
diff --git a/components/Radio/__demo__/custom_render.md b/components/Radio/__demo__/custom_render.md
new file mode 100644
index 00000000..bf00ca9f
--- /dev/null
+++ b/components/Radio/__demo__/custom_render.md
@@ -0,0 +1,117 @@
+---
+order: 5
+title:
+  zh-CN: 自定义节点内容
+  en-US: Custom Render Radio
+---
+
+## zh-CN
+
+可以通过传入函数类型的 `children` 来自定义渲染单选节点。
+
+
+## en-US
+
+Render radio nodes can be customized by passing a function of type 'children'.
+
+```js
+import { Radio, Button, Space, Typography } from '@arco-design/web-react';
+
+ReactDOM.render(
+  <div>
+    <div style={{marginBottom: 20}}>
+      <Radio.Group defaultValue={'Beijing'}>
+        {['Beijing', 'Shanghai', 'Guangzhou'].map((item) => {
+          return (
+            <Radio key={item} value={item}>
+              {({ checked }) => {
+                return <Button key={item} shape="round" type={checked ? 'primary' : 'default'}>{item}</Button>
+              }}
+            </Radio>
+          )
+        })
+        }
+      </Radio.Group>
+    </div>
+    <Radio.Group>
+      {[1, 2].map((item) => {
+        return (
+          <Radio key={item} value={item}>
+            {({ checked }) => {
+              return (
+                <Space
+                  align="start"
+                  className={`custom-radio-card ${checked ? 'custom-radio-card-checked' : ''}`}
+                >
+                  <div className="custom-radio-card-mask">
+                    <div className="custom-radio-card-mask-dot"></div>
+                  </div>
+                  <div>
+                    <div className="custom-radio-card-title">Radio Card {item}</div>
+                    <Typography.Text type="secondary">this is a text</Typography.Text>
+                  </div>
+                </Space>
+              );
+            }}
+          </Radio>
+        );
+      })}
+    </Radio.Group>
+  </div>,
+CONTAINER);
+```
+
+```css
+
+.custom-radio-card {
+  padding: 10px 16px;
+  border: 1px solid var(--color-border-2);
+  border-radius: 4px;
+  width: 250px;
+  box-sizing: border-box;
+}
+
+.custom-radio-card-mask {
+  height: 14px;
+  width: 14px;
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  border-radius: 100%;
+  border: 1px solid var(--color-border-2);
+  box-sizing: border-box;
+}
+
+.custom-radio-card-mask-dot {
+  width: 8px;
+  height: 8px;
+  border-radius: 100%;
+}
+
+.custom-radio-card-title {
+  color: var(--color-text-1);
+  font-size: 14px;
+  font-weight: bold;
+  margin-bottom: 8px;
+}
+
+.custom-radio-card:hover,
+.custom-radio-card-checked,
+.custom-radio-card:hover .custom-radio-card-mask,
+.custom-radio-card-checked  .custom-radio-card-mask{
+  border-color: rgb(var(--primary-6));
+}
+
+.custom-radio-card-checked {
+  background-color: var(--color-primary-light-1);
+}
+
+.custom-radio-card:hover .custom-radio-card-title,
+.custom-radio-card-checked .custom-radio-card-title {
+  color: rgb(var(--primary-6));
+}
+
+.custom-radio-card-checked .custom-radio-card-mask-dot {
+  background-color: rgb(var(--primary-6));
+}
+```
diff --git a/components/Radio/radio.tsx b/components/Radio/radio.tsx
index 7a8341c9..0a00c9b3 100644
--- a/components/Radio/radio.tsx
+++ b/components/Radio/radio.tsx
@@ -1,4 +1,4 @@
-import React, { useContext, PropsWithChildren } from 'react';
+import React, { useContext, PropsWithChildren, useRef } from 'react';
 import cs from '../_util/classNames';
 import Group, { RadioGroupContext } from './group';
 import { ConfigContext } from '../ConfigProvider';
@@ -7,8 +7,10 @@ import useMergeValue from '../_util/hooks/useMergeValue';
 import IconHover from '../_class/icon-hover';
 import { RadioProps } from './interface';
 import useMergeProps from '../_util/hooks/useMergeProps';
+import { isFunction } from '../_util/is';
 
 function Radio(baseProps: PropsWithChildren<RadioProps>) {
+  const inputRef = useRef<HTMLInputElement>(null);
   const { getPrefixCls, componentConfig } = useContext(ConfigContext);
   const props = useMergeProps<PropsWithChildren<RadioProps>>(baseProps, {}, componentConfig?.Radio);
 
@@ -52,9 +54,27 @@ function Radio(baseProps: PropsWithChildren<RadioProps>) {
     !checked && onChange && onChange(true, event);
   };
 
+  const onLabelClick = React.useCallback(
+    (e) => {
+      if (isFunction(props.children)) {
+        // 避免children中含有表单元素造成label无法触发input的onchange的情况
+        e.preventDefault();
+        inputRef.current && inputRef.current.click();
+      }
+      rest.onClick && rest.onClick(e);
+    },
+    [props.children, rest.onClick]
+  );
+
   return (
-    <label {...omit(rest, ['checked', 'onChange'])} style={style} className={classNames}>
+    <label
+      {...omit(rest, ['checked', 'onChange'])}
+      onClick={onLabelClick}
+      style={style}
+      className={classNames}
+    >
       <input
+        ref={inputRef}
         disabled={disabled}
         value={value || ''}
         type="radio"
@@ -68,7 +88,9 @@ function Radio(baseProps: PropsWithChildren<RadioProps>) {
           e.stopPropagation();
         }}
       />
-      {context.type === 'radio' && (
+      {isFunction(children) ? (
+        children({ checked })
+      ) : context.type === 'radio' ? (
         <>
           <IconHover
             prefix={prefixCls}
@@ -79,8 +101,9 @@ function Radio(baseProps: PropsWithChildren<RadioProps>) {
           </IconHover>
           {children && <span className={`${prefixCls}-text`}>{children}</span>}
         </>
+      ) : (
+        context.type === 'button' && <span className={`${prefixCls}-button-inner`}>{children}</span>
       )}
-      {context.type === 'button' && <span className={`${prefixCls}-button-inner`}>{children}</span>}
     </label>
   );
 }
diff --git a/components/Tree/context.tsx b/components/Tree/context.tsx
index 8ef2931a..ef2c5a64 100644
--- a/components/Tree/context.tsx
+++ b/components/Tree/context.tsx
@@ -1,7 +1,7 @@
 import { createContext, DragEvent } from 'react';
 
 import { AvailableVirtualListProps } from '../_class/VirtualList';
-import { NodeProps, TreeDataType, TreeState } from './interface';
+import { NodeProps, TreeDataType, TreeProps, TreeState } from './interface';
 
 export const TreeContext = createContext<{
   icons?: NodeProps['icons'];
@@ -18,6 +18,7 @@ export const TreeContext = createContext<{
     dragPosition: 0 | 1 | -1
   ) => void;
   key2nodeProps?: { [key: string]: NodeProps };
+  actionOnClick?: TreeProps['actionOnClick'];
   getNodeProps?: (node: NodeProps, expandedKeysSet: Set<string>) => NodeProps;
   getTreeState?: () => TreeState;
   onExpandEnd?: (key: string) => void;
diff --git a/components/Tree/index.tsx b/components/Tree/index.tsx
index c3536da9..40bc9a23 100644
--- a/components/Tree/index.tsx
+++ b/components/Tree/index.tsx
@@ -12,7 +12,6 @@ import {
 } from './util';
 import { TreeProps, NodeProps, TreeDataType, NodeInstance, TreeState } from './interface';
 import { TreeContext } from './context';
-import mergeProps from '../_util/mergeProps';
 
 const DefaultFieldNames = {
   key: 'key',
@@ -33,6 +32,7 @@ const defaultProps = {
   selectable: true,
   autoExpandParent: true,
   checkedStrategy: 'all' as const,
+  actionOnClick: 'select',
   allowDrop: () => true,
   fieldNames: DefaultFieldNames,
 };
@@ -51,6 +51,7 @@ const needMergeKeys = [
   'showLine',
   'selectable',
   'allowDrop',
+  'actionOnClick',
 ] as const;
 
 type MergedPropsType = {
@@ -97,16 +98,16 @@ class Tree extends Component<TreeProps, TreeState> {
 
   getMergedProps = (baseProps?): MergedPropsType => {
     const { componentConfig } = this.context;
-    const props = mergeProps<TreeProps>(
-      baseProps || this.props,
-      defaultProps,
-      componentConfig?.Tree
-    );
-    return Object.keys(props).reduce((total, key) => {
-      if (needMergeKeys.indexOf(key as any) > -1) {
-        total[key] = props[key];
+    const props = baseProps || this.props;
+    return needMergeKeys.reduce((_props, key) => {
+      if (props[key] !== undefined) {
+        _props[key] = props[key];
+      } else if (componentConfig?.Tree && componentConfig?.Tree[key] !== undefined) {
+        _props[key] = componentConfig?.Tree[key];
+      } else if (defaultProps[key] !== undefined) {
+        _props[key] = defaultProps[key];
       }
-      return total;
+      return _props;
     }, {});
   };
 
@@ -701,6 +702,7 @@ class Tree extends Component<TreeProps, TreeState> {
       height,
       style,
       icons,
+      actionOnClick,
     } = this.getMergedProps();
     const { loadMore, checkable } = this.props;
     // 兼容旧 APi : height
@@ -739,6 +741,7 @@ class Tree extends Component<TreeProps, TreeState> {
           renderTitle: this.props.renderTitle,
           loadMore: loadMore && this.handleLoadMore,
           allowDrop: this.handleAllowDrop,
+          actionOnClick,
           virtualListProps,
         }}
       >
diff --git a/components/Tree/interface.ts b/components/Tree/interface.ts
index df65b3bd..0076382b 100644
--- a/components/Tree/interface.ts
+++ b/components/Tree/interface.ts
@@ -8,6 +8,8 @@ export type SHOW_ALL = 'all';
 export type SHOW_PARENT = 'parent';
 export type SHOW_CHILD = 'child';
 
+export type ActionOnClick = 'select' | 'check' | 'expand';
+
 export interface TreeState {
   treeData?: TreeProps['treeData'];
   nodeList?: NodeProps[];
@@ -192,6 +194,12 @@ export interface TreeProps {
    * @en Whether to display the connection line
    */
   showLine?: boolean;
+  /**
+   * @zh 点击节点时对应的操作，可以是选中，复选选中，展开/收起
+   * @en The action when click node
+   * @version select
+   */
+  actionOnClick?: ActionOnClick | ActionOnClick[];
   /**
    * @zh 异步加载数据的回调，返回一个 `Promise`。
    * @en Callback when loaded data asynchronously, returning a `Promise`.
diff --git a/components/Tree/node.tsx b/components/Tree/node.tsx
index abc805fd..76a5e095 100644
--- a/components/Tree/node.tsx
+++ b/components/Tree/node.tsx
@@ -154,6 +154,14 @@ function TreeNode(props: PropsWithChildren<NodeProps>, ref) {
     [treeContext.onNodeDragOver]
   );
 
+  const handleCheck = (checked, e) => {
+    const { disableCheckbox, disabled } = props;
+    if (disableCheckbox || disabled) {
+      return;
+    }
+    treeContext.onCheck && treeContext.onCheck(checked, _key, e);
+  };
+
   return (
     <>
       <div style={props.style} className={classNames} ref={ref}>
@@ -180,13 +188,7 @@ function TreeNode(props: PropsWithChildren<NodeProps>, ref) {
             value={_key}
             indeterminate={props.indeterminated}
             checked={props.checked}
-            onChange={(checked, e) => {
-              const { disableCheckbox, disabled } = props;
-              if (disableCheckbox || disabled) {
-                return;
-              }
-              treeContext.onCheck && treeContext.onCheck(checked, _key, e);
-            }}
+            onChange={handleCheck}
           />
         ) : null}
         <span
@@ -206,8 +208,19 @@ function TreeNode(props: PropsWithChildren<NodeProps>, ref) {
             [`${prefixCls}-title-block`]: props.blockNode,
           })}
           onClick={(e) => {
-            const { onSelect } = treeContext;
-            !props.disabled && selectable && onSelect && onSelect(_key, e);
+            const { onSelect, actionOnClick } = treeContext;
+            if (!props.disabled) {
+              const actions = [].concat(actionOnClick);
+              if (selectable && actions.indexOf('select') > -1) {
+                onSelect && onSelect(_key, e);
+              }
+              if (actions.indexOf('expand') > -1) {
+                switchExpandStatus();
+              }
+              if (checkable && actions.indexOf('check') > -1) {
+                handleCheck(!props.checked, e);
+              }
+            }
           }}
           draggable={draggable}
           onDrop={(e) => {
diff --git a/components/Upload/uploader.tsx b/components/Upload/uploader.tsx
index 8cb09b1a..93ffc06b 100644
--- a/components/Upload/uploader.tsx
+++ b/components/Upload/uploader.tsx
@@ -221,6 +221,9 @@ class Uploader extends React.Component<UploaderProps, UploaderState> {
               this.inputRef.value = '';
             }
           }}
+          onClick={e => {
+            e.stopPropagation()
+          }}
         />
         <CSSTransition
           key="trigger-node"


Test patch:
diff --git a/components/Checkbox/__test__/__snapshots__/demo.test.ts.snap b/components/Checkbox/__test__/__snapshots__/demo.test.ts.snap
index 73c7f894..c412e57d 100644
--- a/components/Checkbox/__test__/__snapshots__/demo.test.ts.snap
+++ b/components/Checkbox/__test__/__snapshots__/demo.test.ts.snap
@@ -268,6 +268,144 @@ exports[`renders Checkbox/demo/control.md correctly 1`] = `
 </div>
 `;
 
+exports[`renders Checkbox/demo/custom_render.md correctly 1`] = `
+<div>
+  <div
+    style="margin-bottom:20px"
+  >
+    <span
+      class="arco-checkbox-group arco-checkbox-group-direction-horizontal"
+    >
+      <label
+        checked=""
+        class="arco-checkbox arco-checkbox-checked"
+      >
+        <input
+          checked=""
+          type="checkbox"
+          value="Beijing"
+        />
+        <div
+          class="arco-tag arco-tag-arcoblue arco-tag-checked arco-tag-size-default"
+        >
+          Beijing
+        </div>
+      </label>
+      <label
+        class="arco-checkbox"
+      >
+        <input
+          type="checkbox"
+          value="Shanghai"
+        />
+        <div
+          class="arco-tag arco-tag-checked arco-tag-size-default"
+        >
+          Shanghai
+        </div>
+      </label>
+      <label
+        class="arco-checkbox"
+      >
+        <input
+          type="checkbox"
+          value="Guangzhou"
+        />
+        <div
+          class="arco-tag arco-tag-checked arco-tag-size-default"
+        >
+          Guangzhou
+        </div>
+      </label>
+    </span>
+  </div>
+  <span
+    class="arco-checkbox-group arco-checkbox-group-direction-horizontal"
+  >
+    <label
+      class="arco-checkbox"
+    >
+      <input
+        type="checkbox"
+        value="1"
+      />
+      <div
+        class="arco-space arco-space-horizontal arco-space-align-start custom-checkbox-card "
+      >
+        <div
+          class="arco-space-item"
+          style="margin-right:8px"
+        >
+          <div
+            class="custom-checkbox-card-mask"
+          >
+            <div
+              class="custom-checkbox-card-mask-dot"
+            />
+          </div>
+        </div>
+        <div
+          class="arco-space-item"
+        >
+          <div>
+            <div
+              class="custom-checkbox-card-title"
+            >
+              Checkbox Card 1
+            </div>
+            <span
+              class="arco-typography arco-typography-secondary"
+            >
+              this is a text
+            </span>
+          </div>
+        </div>
+      </div>
+    </label>
+    <label
+      class="arco-checkbox"
+    >
+      <input
+        type="checkbox"
+        value="2"
+      />
+      <div
+        class="arco-space arco-space-horizontal arco-space-align-start custom-checkbox-card "
+      >
+        <div
+          class="arco-space-item"
+          style="margin-right:8px"
+        >
+          <div
+            class="custom-checkbox-card-mask"
+          >
+            <div
+              class="custom-checkbox-card-mask-dot"
+            />
+          </div>
+        </div>
+        <div
+          class="arco-space-item"
+        >
+          <div>
+            <div
+              class="custom-checkbox-card-title"
+            >
+              Checkbox Card 2
+            </div>
+            <span
+              class="arco-typography arco-typography-secondary"
+            >
+              this is a text
+            </span>
+          </div>
+        </div>
+      </div>
+    </label>
+  </span>
+</div>
+`;
+
 exports[`renders Checkbox/demo/disabled.md correctly 1`] = `
 <div>
   <label
diff --git a/components/Radio/__test__/__snapshots__/demo.test.ts.snap b/components/Radio/__test__/__snapshots__/demo.test.ts.snap
index ee2ba50f..f866d3a5 100644
--- a/components/Radio/__test__/__snapshots__/demo.test.ts.snap
+++ b/components/Radio/__test__/__snapshots__/demo.test.ts.snap
@@ -58,6 +58,152 @@ exports[`renders Radio/demo/basic.md correctly 1`] = `
 </div>
 `;
 
+exports[`renders Radio/demo/custom_render.md correctly 1`] = `
+<div>
+  <div
+    style="margin-bottom:20px"
+  >
+    <div
+      class="arco-radio-group arco-radio-size-default arco-radio-mode-outline"
+    >
+      <label
+        class="arco-radio arco-radio-checked"
+      >
+        <input
+          checked=""
+          type="radio"
+          value="Beijing"
+        />
+        <button
+          class="arco-btn arco-btn-primary arco-btn-size-default arco-btn-shape-round"
+          type="button"
+        >
+          <span>
+            Beijing
+          </span>
+        </button>
+      </label>
+      <label
+        class="arco-radio"
+      >
+        <input
+          type="radio"
+          value="Shanghai"
+        />
+        <button
+          class="arco-btn arco-btn-secondary arco-btn-size-default arco-btn-shape-round"
+          type="button"
+        >
+          <span>
+            Shanghai
+          </span>
+        </button>
+      </label>
+      <label
+        class="arco-radio"
+      >
+        <input
+          type="radio"
+          value="Guangzhou"
+        />
+        <button
+          class="arco-btn arco-btn-secondary arco-btn-size-default arco-btn-shape-round"
+          type="button"
+        >
+          <span>
+            Guangzhou
+          </span>
+        </button>
+      </label>
+    </div>
+  </div>
+  <div
+    class="arco-radio-group arco-radio-size-default arco-radio-mode-outline"
+  >
+    <label
+      class="arco-radio"
+    >
+      <input
+        type="radio"
+        value="1"
+      />
+      <div
+        class="arco-space arco-space-horizontal arco-space-align-start custom-radio-card "
+      >
+        <div
+          class="arco-space-item"
+          style="margin-right:8px"
+        >
+          <div
+            class="custom-radio-card-mask"
+          >
+            <div
+              class="custom-radio-card-mask-dot"
+            />
+          </div>
+        </div>
+        <div
+          class="arco-space-item"
+        >
+          <div>
+            <div
+              class="custom-radio-card-title"
+            >
+              Radio Card 1
+            </div>
+            <span
+              class="arco-typography arco-typography-secondary"
+            >
+              this is a text
+            </span>
+          </div>
+        </div>
+      </div>
+    </label>
+    <label
+      class="arco-radio"
+    >
+      <input
+        type="radio"
+        value="2"
+      />
+      <div
+        class="arco-space arco-space-horizontal arco-space-align-start custom-radio-card "
+      >
+        <div
+          class="arco-space-item"
+          style="margin-right:8px"
+        >
+          <div
+            class="custom-radio-card-mask"
+          >
+            <div
+              class="custom-radio-card-mask-dot"
+            />
+          </div>
+        </div>
+        <div
+          class="arco-space-item"
+        >
+          <div>
+            <div
+              class="custom-radio-card-title"
+            >
+              Radio Card 2
+            </div>
+            <span
+              class="arco-typography arco-typography-secondary"
+            >
+              this is a text
+            </span>
+          </div>
+        </div>
+      </div>
+    </label>
+  </div>
+</div>
+`;
+
 exports[`renders Radio/demo/direction.md correctly 1`] = `
 <div>
   <div
diff --git a/components/Tree/__test__/index.test.tsx b/components/Tree/__test__/index.test.tsx
index 5bb0f8b4..398a0fa2 100644
--- a/components/Tree/__test__/index.test.tsx
+++ b/components/Tree/__test__/index.test.tsx
@@ -432,4 +432,57 @@ describe('Tree', () => {
       .find(`.arco-checkbox`)
       .hasClass('arco-checkbox-indeterminate');
   });
+
+  it('should be expanded when click node', () => {
+    const mockExpand = jest.fn();
+    const mockSelected = jest.fn();
+    const wrapper = mount(
+      <Tree
+        defaultExpandedKeys={[]}
+        actionOnClick="expand"
+        onSelect={mockSelected}
+        onExpand={mockExpand}
+      >
+        {generatorTreeNodes(data)}
+      </Tree>
+    );
+
+    expect(wrapper.find('.arco-tree-node-title-text').at(1).text()).toBe('史塔克家族');
+
+    wrapper
+      .find(`${prefixCls}-node`)
+      .first()
+      .find(`${prefixCls}-node-title`)
+      .first()
+      .simulate('click');
+    expect(mockExpand.mock.calls).toHaveLength(1);
+    expect(mockSelected.mock.calls).toHaveLength(0);
+    expect(wrapper.find('.arco-tree-node-title-text').at(1).text()).toBe('小恶魔');
+  });
+
+  it('should be checked and expand when click node', () => {
+    let checkedKeys;
+    const wrapper = mount(
+      <Tree
+        defaultExpandedKeys={[]}
+        checkable
+        onCheck={(keys) => (checkedKeys = keys)}
+        actionOnClick={['check', 'expand']}
+      >
+        {generatorTreeNodes(data)}
+      </Tree>
+    );
+
+    expect(wrapper.find('.arco-tree-node-title-text').at(1).text()).toBe('史塔克家族');
+    wrapper
+      .find(`${prefixCls}-node`)
+      .first()
+      .find(`${prefixCls}-node-title`)
+      .first()
+      .simulate('click');
+    expect(wrapper.find('.arco-tree-node-title-text').at(1).text()).toBe('小恶魔');
+    expect(wrapper.find('.arco-checkbox-checked')).toHaveLength(2);
+
+    expect(checkedKeys).toEqual(['node1', 'node1-1', 'node1-1-1']);
+  });
 });