Skip to content

Commit 557f149

Browse files
huangchen1031github-actions[bot]uyarn
authored
feat(Cascader): support custom option (#3565)
* feat(cascader): 实现option属性 * chore: update snapshot * chore: add context params --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: wū yāng <uyarnchen@gmail.com>
1 parent 4a96164 commit 557f149

File tree

10 files changed

+201
-8
lines changed

10 files changed

+201
-8
lines changed

packages/components/cascader/Cascader.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ const Cascader: React.FC<CascaderProps> = (originalProps) => {
204204
{props.panelTopContent && parseTNode(props.panelTopContent)}
205205
<Panel
206206
cascaderContext={cascaderContext}
207-
{...pick(props, ['trigger', 'onChange', 'empty', 'loading', 'loadingText'])}
207+
{...pick(props, ['trigger', 'onChange', 'empty', 'loading', 'loadingText', 'option'])}
208208
></Panel>
209209
{props.panelBottomContent && parseTNode(props.panelBottomContent)}
210210
</>

packages/components/cascader/CascaderPanel.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const CascaderPanel: React.FC<CascaderProps> = (originalProps) => {
1818
className={classNames(props.className)}
1919
style={props.style}
2020
cascaderContext={cascaderContext}
21-
{...pick(props, ['trigger', 'onChange', 'empty'])}
21+
{...pick(props, ['trigger', 'onChange', 'empty', 'option'])}
2222
></Panel>
2323
);
2424
};
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import React, { useMemo, useState } from 'react';
2+
import { Cascader, Space } from 'tdesign-react';
3+
import type { CascaderProps, CascaderValue } from 'tdesign-react';
4+
5+
const optionRender = ({ item }) => (
6+
<div className="tdesign-demo__user-option" style={{ display: 'flex' }}>
7+
<img src="https://tdesign.gtimg.com/site/avatar.jpg" style={{ maxWidth: 28, maxHeight: 28, borderRadius: '50%' }} />
8+
<div className="tdesign-demo__user-option-info" style={{ marginLeft: 16, lineHeight: '28px' }}>
9+
<span>{item.label}</span>
10+
</div>
11+
</div>
12+
);
13+
14+
const getDeepOptions = (options) => {
15+
if (!options) return null;
16+
return options.map((item) => ({
17+
...item,
18+
children: getDeepOptions(item.children),
19+
// content 自定义下拉选项关键代码
20+
content: optionRender({ item }),
21+
}));
22+
};
23+
24+
export default function Example() {
25+
const [value, setValue] = useState<CascaderValue>([]);
26+
const [options] = useState([
27+
{
28+
label: '选项一',
29+
value: '1',
30+
children: [
31+
{
32+
label: '子选项一',
33+
value: '1.1',
34+
},
35+
{
36+
label: '子选项二',
37+
value: '1.2',
38+
},
39+
{
40+
label: '子选项三',
41+
value: '1.3',
42+
},
43+
],
44+
},
45+
{
46+
label: '选项二',
47+
value: '2',
48+
children: [
49+
{
50+
label: '子选项一',
51+
value: '2.1',
52+
},
53+
{
54+
label: '子选项二',
55+
value: '2.2',
56+
},
57+
],
58+
},
59+
]);
60+
61+
const onChange: CascaderProps['onChange'] = (value) => {
62+
setValue(value);
63+
};
64+
65+
const optionsData = useMemo(() => getDeepOptions(options), [options]);
66+
67+
return (
68+
<Space direction="vertical">
69+
{/* 方式一:使用 options 自定义下拉选项内容 */}
70+
<Cascader options={optionsData} filterable onChange={onChange} value={value} size="medium" clearable />
71+
{/* 方式二:使用option传参自定义下拉选项内容 */}
72+
<Cascader
73+
option={optionRender}
74+
options={options}
75+
filterable
76+
onChange={onChange}
77+
value={value}
78+
size="medium"
79+
clearable
80+
/>
81+
</Space>
82+
);
83+
}

packages/components/cascader/cascader.en-US.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ loadingText | TNode | - | Typescript:`string \| TNode`。[see more ts definiti
2828
max | Number | 0 | \- | N
2929
minCollapsedNum | Number | 0 | \- | N
3030
multiple | Boolean | false | \- | N
31-
option | TElement | - | customize one option。Typescript:`TNode<{ item: CascaderOption; index: number }>`[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/common.ts) | N
31+
option | TElement | - | customize one option。Typescript:`TNode<{ item: CascaderOption; index: number; context: { node: TreeNodeModel<CascaderOption> } }>`[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/common.ts) | N
3232
options | Array | [] | Typescript:`Array<CascaderOption>` | N
3333
panelBottomContent | TNode | - | bottom content of the cascader panel。Typescript:`string \| TNode`[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/common.ts) | N
3434
panelTopContent | TNode | - | top content of the cascader panel。Typescript:`string \| TNode`[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/common.ts) | N

packages/components/cascader/cascader.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ loadingText | TNode | - | 远程加载时显示的文字,支持自定义。如
2828
max | Number | 0 | 用于控制多选数量,值为 0 则不限制 | N
2929
minCollapsedNum | Number | 0 | 最小折叠数量,用于多选情况下折叠选中项,超出该数值的选中项折叠。值为 0 则表示不折叠 | N
3030
multiple | Boolean | false | 是否允许多选 | N
31-
option | TElement | - | 自定义单个级联选项。TS 类型:`TNode<{ item: CascaderOption; index: number }>`[通用类型定义](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/common.ts) | N
31+
option | TElement | - | 自定义单个级联选项。TS 类型:`TNode<{ item: CascaderOption; index: number, context: { node: TreeNodeModel<CascaderOption> } }>`[通用类型定义](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/common.ts) | N
3232
options | Array | [] | 可选项数据源。TS 类型:`Array<CascaderOption>` | N
3333
panelBottomContent | TNode | - | 面板内的底部内容。TS 类型:`string \| TNode`[通用类型定义](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/common.ts) | N
3434
panelTopContent | TNode | - | 面板内的顶部内容。TS 类型:`string \| TNode`[通用类型定义](https://github.com/Tencent/tdesign-react/blob/develop/packages/components/common.ts) | N

packages/components/cascader/components/Item.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const Item = forwardRef(
1919
(
2020
props: {
2121
node: TreeNode;
22+
optionChild: React.ReactNode;
2223
cascaderContext: CascaderContextType;
2324
onClick: (ctx: TreeNode) => void;
2425
onChange: (ctx: TreeNode | { e: boolean; node: TreeNode }) => void;
@@ -28,6 +29,7 @@ const Item = forwardRef(
2829
) => {
2930
const {
3031
node,
32+
optionChild,
3133
cascaderContext: { multiple },
3234
onClick,
3335
onChange,
@@ -60,6 +62,11 @@ const Item = forwardRef(
6062

6163
const RenderLabelInner = (node: TreeNode, cascaderContext: CascaderContextType) => {
6264
const { inputVal } = cascaderContext;
65+
66+
if (!inputVal && optionChild) {
67+
return optionChild;
68+
}
69+
6370
const labelText = inputVal ? getFullPathLabel(node) : node.label;
6471

6572
if (inputVal) {
@@ -76,6 +83,7 @@ const Item = forwardRef(
7683
}
7784
return doms;
7885
}
86+
7987
return labelText;
8088
};
8189

packages/components/cascader/components/Panel.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,16 @@ import { expendClickEffect, valueChangeEffect } from '../core/effect';
1010
import { TreeNode, CascaderContextType } from '../interface';
1111
import { TdCascaderProps } from '../type';
1212
import { StyledProps } from '../../common';
13+
import parseTNode from '../../_util/parseTNode';
1314

1415
export interface CascaderPanelProps
1516
extends StyledProps,
16-
Pick<TdCascaderProps, 'trigger' | 'empty' | 'onChange' | 'loading' | 'loadingText'> {
17+
Pick<TdCascaderProps, 'trigger' | 'empty' | 'onChange' | 'loading' | 'loadingText' | 'option'> {
1718
cascaderContext: CascaderContextType;
1819
}
1920

2021
const Panel = (props: CascaderPanelProps) => {
21-
const { cascaderContext } = props;
22+
const { cascaderContext, option } = props;
2223

2324
const panels = useMemo(() => getPanels(cascaderContext.treeNodes), [cascaderContext.treeNodes]);
2425

@@ -31,10 +32,11 @@ const Panel = (props: CascaderPanelProps) => {
3132
const [global] = useLocaleReceiver('cascader');
3233
const COMPONENT_NAME = `${classPrefix}-cascader`;
3334

34-
const renderItem = (node: TreeNode, index) => (
35+
const renderItem = (node: TreeNode, index: number) => (
3536
<Item
3637
key={index}
3738
node={node}
39+
optionChild={node.data.content || parseTNode(option, { item: node.data, index, context: { node } })}
3840
cascaderContext={cascaderContext}
3941
onClick={() => {
4042
handleExpand(node, 'click');

packages/components/cascader/type.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ export interface TdCascaderProps<CascaderOption extends TreeOptionData = TreeOpt
113113
/**
114114
* 自定义单个级联选项
115115
*/
116-
option?: TNode<{ item: CascaderOption; index: number }>;
116+
option?: TNode<{ item: CascaderOption; index: number; context: { node: TreeNodeModel<CascaderOption> } }>;
117117
/**
118118
* 可选项数据源
119119
* @default []

test/snap/__snapshots__/csr.test.jsx.snap

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19778,6 +19778,102 @@ exports[`csr snapshot test > csr test packages/components/cascader/_example/coll
1977819778
</div>
1977919779
`;
1978019780

19781+
exports[`csr snapshot test > csr test packages/components/cascader/_example/custom-options.tsx 1`] = `
19782+
<div>
19783+
<div
19784+
class="t-space t-space-vertical"
19785+
style="gap: 16px;"
19786+
>
19787+
<div
19788+
class="t-space-item"
19789+
>
19790+
<div
19791+
class="t-cascader t-select-input t-select-input--empty"
19792+
>
19793+
<div
19794+
class="t-input__wrap"
19795+
spellcheck="false"
19796+
value=""
19797+
>
19798+
<div
19799+
class="t-input t-align-left t-input--suffix"
19800+
>
19801+
<input
19802+
class="t-input__inner"
19803+
placeholder="请选择"
19804+
type="text"
19805+
value=""
19806+
/>
19807+
<span
19808+
class="t-input__suffix t-input__suffix-icon"
19809+
>
19810+
<svg
19811+
class="t-fake-arrow t-cascader__icon"
19812+
fill="none"
19813+
height="16"
19814+
viewBox="0 0 16 16"
19815+
width="16"
19816+
xmlns="http://www.w3.org/2000/svg"
19817+
>
19818+
<path
19819+
d="M3.75 5.7998L7.99274 10.0425L12.2361 5.79921"
19820+
stroke="black"
19821+
stroke-opacity="0.9"
19822+
stroke-width="1.3"
19823+
/>
19824+
</svg>
19825+
</span>
19826+
</div>
19827+
</div>
19828+
</div>
19829+
</div>
19830+
<div
19831+
class="t-space-item"
19832+
>
19833+
<div
19834+
class="t-cascader t-select-input t-select-input--empty"
19835+
>
19836+
<div
19837+
class="t-input__wrap"
19838+
spellcheck="false"
19839+
value=""
19840+
>
19841+
<div
19842+
class="t-input t-align-left t-input--suffix"
19843+
>
19844+
<input
19845+
class="t-input__inner"
19846+
placeholder="请选择"
19847+
type="text"
19848+
value=""
19849+
/>
19850+
<span
19851+
class="t-input__suffix t-input__suffix-icon"
19852+
>
19853+
<svg
19854+
class="t-fake-arrow t-cascader__icon"
19855+
fill="none"
19856+
height="16"
19857+
viewBox="0 0 16 16"
19858+
width="16"
19859+
xmlns="http://www.w3.org/2000/svg"
19860+
>
19861+
<path
19862+
d="M3.75 5.7998L7.99274 10.0425L12.2361 5.79921"
19863+
stroke="black"
19864+
stroke-opacity="0.9"
19865+
stroke-width="1.3"
19866+
/>
19867+
</svg>
19868+
</span>
19869+
</div>
19870+
</div>
19871+
</div>
19872+
</div>
19873+
</div>
19874+
</div>
19875+
`;
19876+
1978119877
exports[`csr snapshot test > csr test packages/components/cascader/_example/disabled.tsx 1`] = `
1978219878
<div>
1978319879
<div
@@ -138219,6 +138315,8 @@ exports[`ssr snapshot test > ssr test packages/components/cascader/_example/chec
138219138315

138220138316
exports[`ssr snapshot test > ssr test packages/components/cascader/_example/collapsed.tsx 1`] = `"<div style="gap:16px" class="t-space t-space-vertical"><div class="t-space-item"><div class="t-cascader t-select-input t-select-input--multiple t-select-input--empty"><div class="t-input__wrap t-tag-input t-tag-input--break-line t-tag-input__with-suffix-icon t-is-empty" value="" spellcheck="false"><div class="t-input t-is-readonly t-align-left t-input--prefix t-input--suffix"><div class="t-input__prefix"></div><input placeholder="请选择" type="text" class="t-input__inner" readonly="" value=""/><span class="t-input__input-pre">请选择</span><span class="t-input__suffix t-input__suffix-icon"><svg class="t-fake-arrow t-cascader__icon" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3.75 5.7998L7.99274 10.0425L12.2361 5.79921" stroke="black" stroke-opacity="0.9" stroke-width="1.3"></path></svg></span></div></div></div></div><div class="t-space-item"><div class="t-cascader t-select-input t-select-input--multiple t-select-input--empty"><div class="t-input__wrap t-tag-input t-tag-input--break-line t-tag-input__with-suffix-icon t-is-empty" value="" spellcheck="false"><div class="t-input t-is-readonly t-align-left t-input--prefix t-input--suffix"><div class="t-input__prefix"></div><input placeholder="请选择" type="text" class="t-input__inner" readonly="" value=""/><span class="t-input__input-pre">请选择</span><span class="t-input__suffix t-input__suffix-icon"><svg class="t-fake-arrow t-cascader__icon" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3.75 5.7998L7.99274 10.0425L12.2361 5.79921" stroke="black" stroke-opacity="0.9" stroke-width="1.3"></path></svg></span></div></div></div></div></div>"`;
138221138317

138318+
exports[`ssr snapshot test > ssr test packages/components/cascader/_example/custom-options.tsx 1`] = `"<div style="gap:16px" class="t-space t-space-vertical"><div class="t-space-item"><div class="t-cascader t-select-input t-select-input--empty"><div class="t-input__wrap" value="" spellcheck="false"><div class="t-input t-align-left t-input--suffix"><input placeholder="请选择" type="text" class="t-input__inner" value=""/><span class="t-input__suffix t-input__suffix-icon"><svg class="t-fake-arrow t-cascader__icon" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3.75 5.7998L7.99274 10.0425L12.2361 5.79921" stroke="black" stroke-opacity="0.9" stroke-width="1.3"></path></svg></span></div></div></div></div><div class="t-space-item"><div class="t-cascader t-select-input t-select-input--empty"><div class="t-input__wrap" value="" spellcheck="false"><div class="t-input t-align-left t-input--suffix"><input placeholder="请选择" type="text" class="t-input__inner" value=""/><span class="t-input__suffix t-input__suffix-icon"><svg class="t-fake-arrow t-cascader__icon" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3.75 5.7998L7.99274 10.0425L12.2361 5.79921" stroke="black" stroke-opacity="0.9" stroke-width="1.3"></path></svg></span></div></div></div></div></div>"`;
138319+
138222138320
exports[`ssr snapshot test > ssr test packages/components/cascader/_example/disabled.tsx 1`] = `"<div style="gap:16px" class="t-space t-space-vertical"><div class="t-space-item"><div class="t-cascader t-select-input t-select-input--empty"><div class="t-input__wrap" value="" spellcheck="false"><div class="t-input t-is-disabled t-align-left t-input--suffix"><input placeholder="请选择" type="text" class="t-input__inner" readonly="" disabled="" value=""/><span class="t-input__suffix t-input__suffix-icon"><svg class="t-fake-arrow t-cascader__icon t-is-disabled" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3.75 5.7998L7.99274 10.0425L12.2361 5.79921" stroke="black" stroke-opacity="0.9" stroke-width="1.3"></path></svg></span></div></div></div></div><div class="t-space-item"><div class="t-cascader t-select-input t-select-input--multiple t-select-input--empty"><div class="t-input__wrap t-tag-input t-tag-input--break-line t-tag-input__with-suffix-icon t-is-empty" value="" spellcheck="false"><div class="t-input t-is-readonly t-is-disabled t-align-left t-input--prefix t-input--suffix"><div class="t-input__prefix"></div><input placeholder="请选择" type="text" class="t-input__inner" readonly="" disabled="" value=""/><span class="t-input__input-pre">请选择</span><span class="t-input__suffix t-input__suffix-icon"><svg class="t-fake-arrow t-cascader__icon t-is-disabled" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3.75 5.7998L7.99274 10.0425L12.2361 5.79921" stroke="black" stroke-opacity="0.9" stroke-width="1.3"></path></svg></span></div></div></div></div></div>"`;
138223138321

138224138322
exports[`ssr snapshot test > ssr test packages/components/cascader/_example/ellipsis.tsx 1`] = `"<div class="t-cascader t-select-input t-select-input--empty"><div class="t-input__wrap" value="" spellcheck="false"><div class="t-input t-align-left t-input--suffix"><input placeholder="请选择" type="text" class="t-input__inner" value=""/><span class="t-input__suffix t-input__suffix-icon"><svg class="t-fake-arrow t-cascader__icon" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3.75 5.7998L7.99274 10.0425L12.2361 5.79921" stroke="black" stroke-opacity="0.9" stroke-width="1.3"></path></svg></span></div></div></div>"`;

0 commit comments

Comments
 (0)