Skip to content

Commit

Permalink
feat: add support for CascadeTree and MultiCascadeTree (#3563)
Browse files Browse the repository at this point in the history
* feat(CascadeTree): add support for CascadeTree

* docs(CascadeTree): update example

* docs(CascadeTree): add example in CascadeTree

* refactor: add SearchPanel and useSearch

* feat: add support for `searchable` and `onSearch`

* test: add tests for search

* docs: update component.config.json

* fix: adjust the gap between the panel and the search box

* docs(Cascader): update props

* feat: add support for MultiCascader

* fix: update tests

* test: add tests

* docs: add accessibility

* fix: fix search list style error

* fix: use react hook to refactor some code
  • Loading branch information
simonguo committed Mar 19, 2024
1 parent 0cad184 commit 524fc7a
Show file tree
Hide file tree
Showing 84 changed files with 4,443 additions and 2,540 deletions.
384 changes: 115 additions & 269 deletions docs/package-lock.json

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions docs/pages/_common/types/item-data-type.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
### `ts:ItemDataType`

```ts
interface ItemDataType {
interface ItemDataType<V> {
/** The value of the option corresponds to the `valueKey` in the data. **/
value: string;
value: V;

/** The content displayed by the option corresponds to the `labelKey` in the data. **/
label: ReactNode;
Expand All @@ -12,7 +12,7 @@ interface ItemDataType {
* The data of the child option corresponds to the `childrenKey` in the data.
* Properties owned by tree structure components, such as TreePicker, Cascader.
*/
children?: ItemDataType[];
children?: ItemDataType<V>[];

/**
* Properties of grouping functional components, such as CheckPicker, InputPicker
Expand Down
70 changes: 70 additions & 0 deletions docs/pages/components/cascade-tree/en-US/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# CascadeTree

CascadeTree is a component that displays tree-structured data in columns.

## Import

<!--{include:<import-guide>}-->

## Examples

### Basic

<!--{include:`basic.md`}-->

### Custom options

<!--{include:`custom.md`}-->

### Async Data

This tree allows the use of the `getChildren` option and the length of the children field on the node to be 0 to load children asynchronously.

<!--{include:`async.md`}-->

### Searchable

<!--{include:`searchable.md`}-->

## Accessibility

### ARIA properties

- CascadeTree has role `tree`.
- Each column has role `group`.
- Each item has role `treeitem`.
- Each item has `aria-setsize` equal to the number of items in the column.
- Each item has `aria-level` equal to the column index.
- The selected item has `aria-selected="true"`.
- The disabled item has `aria-disabled="true"`.
- The search input has role `searchbox`.

## Props

### `<CascadeTree>`

<!-- prettier-sort-markdown-table -->

| Property | Type`(Default)` | Description |
| ------------------ | ---------------------------------------------------------------------------------- | ------------------------------------------------------ |
| childrenKey | string `('children')` | Set children key in data |
| classPrefix | string `('cascader-tree')` | The prefix of the component CSS class |
| columnHeight | number | Sets the height of the menu |
| columnWidth | number | Sets the width of the menu |
| data \* | [ItemDataType][item][] | The data of component |
| defaultValue | string | Specifies the default value of the selected items |
| disabledItemValues | string[] | Disabled items |
| getChildren | (item: [ItemDataType][item]) => Promise&lt;[ItemDataType][item][]&gt; | Asynchronously load the children of the tree node. |
| labelKey | string `('label')` | Set label key in data |
| onChange | (value: string, event: SyntheticEvent) => void | Callback fired when value changes |
| onSearch | (value: string, event) => void | Callback fired when search value changes |
| onSelect | (item: [ItemDataType][item], selectedPaths: [ItemDataType][item][], event) => void | Callback fired when item is selected |
| renderColumn | (childNodes: ReactNode, column: { items, parentItem, layer}) => ReactNode | Customizing the Rendering Menu list |
| renderTreeNode | (node: ReactNode, item: [ItemDataType][item]) => ReactNode | Custom render menu items |
| searchable | boolean | Whether to enable search |
| value | string | Specifies the values of the selected items(Controlled) |
| valueKey | string `('value')` | Set value key in data |

<!--{include:(_common/types/item-data-type.md)}-->

[item]: #code-ts-item-data-type-code
36 changes: 36 additions & 0 deletions docs/pages/components/cascade-tree/fragments/async.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<!--start-code-->

```js
import { CascadeTree } from 'rsuite';
import FolderFillIcon from '@rsuite/icons/FolderFill';
import PageIcon from '@rsuite/icons/Page';
import { mockAsyncData } from './mock';

const [getNodes, fetchNodes] = mockAsyncData();
const initialData = getNodes(5);

const App = () => {
return (
<div style={{ overflow: 'auto' }}>
<CascadeTree
columnWidth={180}
data={initialData}
getChildren={node => {
return fetchNodes(node.id);
}}
renderTreeNode={(label, item) => {
return (
<>
{item.children ? <FolderFillIcon /> : <PageIcon />} {label}
</>
);
}}
/>
</div>
);
};

ReactDOM.render(<App />, document.getElementById('root'));
```

<!--end-code-->
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<!--start-code-->

```js
import { Cascader } from 'rsuite';
import { CascadeTree } from 'rsuite';
import { mockTreeData } from './mock';

const data = mockTreeData({
Expand All @@ -12,9 +12,13 @@ const data = mockTreeData({
}
});

const App = () => (
<Cascader inline data={data} searchable={false} menuHeight="auto" menuWidth={180} />
);
const App = () => {
return (
<>
<CascadeTree data={data} />
</>
);
};

ReactDOM.render(<App />, document.getElementById('root'));
```
Expand Down
55 changes: 55 additions & 0 deletions docs/pages/components/cascade-tree/fragments/custom.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<!--start-code-->

```js
import { CascadeTree } from 'rsuite';
import AdminIcon from '@rsuite/icons/Admin';
import { mockTreeData } from './mock';

const headers = ['Job Area', 'Job Type', 'Name'];
const data = mockTreeData({
limits: [3, 3, 4],
labels: (layer, value, faker) => {
const methodName = ['jobArea', 'jobType', 'firstName'];
return faker.person[methodName[layer]]();
}
});

const Column = ({ header, children }) => {
return (
<div>
<div
style={{
background: '#154c94',
padding: '4px 10px',
color: ' #fff',
textAlign: 'center'
}}
>
{header}
</div>
{children}
</div>
);
};

const App = () => (
<CascadeTree
data={data}
columnWidth={180}
renderTreeNode={(label, node) => {
return (
<>
<AdminIcon /> {label}
</>
);
}}
renderColumn={(childNodes, { layer }) => {
return <Column header={headers[layer]}> {childNodes}</Column>;
}}
/>
);

ReactDOM.render(<App />, document.getElementById('root'));
```

<!--end-code-->
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<!--start-code-->

```js
import { MultiCascader } from 'rsuite';
import { CascadeTree } from 'rsuite';
import { mockTreeData } from './mock';

const data = mockTreeData({
Expand All @@ -12,11 +12,13 @@ const data = mockTreeData({
}
});

const App = () => (
<>
<MultiCascader inline data={data} searchable={false} menuHeight="auto" menuWidth={180} />
</>
);
const App = () => {
return (
<>
<CascadeTree searchable data={data} />
</>
);
};

ReactDOM.render(<App />, document.getElementById('root'));
```
Expand Down
50 changes: 50 additions & 0 deletions docs/pages/components/cascade-tree/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React from 'react';
import { CascadeTree, Button, RadioGroup, Radio } from 'rsuite';
import DefaultPage from '@/components/Page';
import AdminIcon from '@rsuite/icons/Admin';
import FolderFillIcon from '@rsuite/icons/FolderFill';
import PageIcon from '@rsuite/icons/Page';
import ImportGuide from '@/components/ImportGuide';

import {
importFakerString,
mockAsyncData,
mockAsyncDataString,
mockTreeData,
mockTreeDataToString,
sandboxFakerVersion
} from '@/utils/mock';

const mockfile = {
name: 'mock.js',
content: [importFakerString, mockTreeDataToString, mockAsyncDataString].join('\n')
};

const sandboxDependencies = {
...sandboxFakerVersion
};

const inDocsComponents = {
'import-guide': () => <ImportGuide components={['CascadeTree']} />
};

export default function Page() {
return (
<DefaultPage
inDocsComponents={inDocsComponents}
dependencies={{
CascadeTree,
Button,
RadioGroup,
Radio,
AdminIcon,
FolderFillIcon,
PageIcon,
mockAsyncData,
mockTreeData
}}
sandboxDependencies={sandboxDependencies}
sandboxFiles={[mockfile]}
/>
);
}
69 changes: 69 additions & 0 deletions docs/pages/components/cascade-tree/zh-CN/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# CascadeTree 级联树

CascadeTree 是一个按列显示树形结构数据的组件。

## 获取组件

<!--{include:<import-guide>}-->

## 演示

### 默认

<!--{include:`basic.md`}-->

### 自定义选项

<!--{include:`custom.md`}-->

### 异步数据

可以通过 `getChildren` 属性和树节点上的 `children` 字段 `length``0` 以异步加载子级。

<!--{include:`async.md`}-->

### 可搜索

<!--{include:`searchable.md`}-->

## 可访问性

### ARIA 属性

- CascadeTree 具有 `tree` 角色。
- 每个列具有 `group` 角色。
- 每个选项具有 `treeitem` 角色。
- 每个选项的 `aria-setsize` 等于列中的选项数。
- 每个选项的 `aria-level` 等于列索引。
- 选中的选项具有 `aria-selected="true"`
- 禁用的选项具有 `aria-disabled="true"`
- 搜索输入具有 `searchbox` 角色。

## Props

### `<CascadeTree>`

<!-- prettier-sort-markdown-table -->

| 属性名称 | 类型`(默认值)` | 描述 |
| ------------------ | ---------------------------------------------------------------------------------- | ------------------------------------ |
| childrenKey | string `('children')` | 设置选项子节点在 `data` 中的 `key` |
| classPrefix | string `('cascade-tree')` | 组件 CSS 类的前缀 |
| columnHeight | number | 设置菜单的高度 |
| columnWidth | number | 设置菜单的宽度 |
| data \* | [ItemDataType][item][] | 组件数据 |
| defaultValue | string | 默认值 |
| disabledItemValues | string[] | 禁用选项 |
| getChildren | (item: [ItemDataType][item]) => Promise&lt;[ItemDataType][item][]&gt; | 异步加载树节点的子级 |
| labelKey | string `('label')` | 设置选项显示内容在 `data` 中的 `key` |
| onChange | (value: string, event: SyntheticEvent) => void | 值变化后的回调函数 |
| onSearch | (value: string, event) => void | 搜索值变化后的回调函数 |
| onSelect | (item: [ItemDataType][item], selectedPaths: [ItemDataType][item][], event) => void | 选项被点击选择后的回调函数 |
| renderColumn | (childNodes: ReactNode, column: { items, parentItem, layer}) => ReactNode | 自定义渲染菜单列表 |
| renderTreeNode | (node: ReactNode, item: [ItemDataType][item]) => ReactNode | 自定义选项 |
| searchable | boolean | 是否启用搜索 |
| value | string | 设置值(受控) |

<!--{include:(_common/types/item-data-type.md)}-->

[item]: #code-ts-item-data-type-code

0 comments on commit 524fc7a

Please sign in to comment.