Skip to content

Commit 98cb7bb

Browse files
author
mingzhixu
committed
feat(cascader): cascader
Tencent#426
1 parent 3a541f7 commit 98cb7bb

File tree

7 files changed

+159
-118
lines changed

7 files changed

+159
-118
lines changed

src/cascader/Cascader.tsx

+37-23
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { Popup } from 'tdesign-mobile-react/popup';
88
import { Radio, RadioGroup } from 'tdesign-mobile-react/radio';
99
import Tabs from 'tdesign-mobile-react/tabs';
1010
import TabContext from 'tdesign-mobile-react/tabs/context';
11-
import { StyledProps, TNode, TreeKeysType, TreeOptionData } from '../common';
11+
import { StyledProps, TNode, TreeOptionData } from '../common';
1212
import { usePrefixClass } from '../hooks/useClass';
1313
import useDefaultProps from '../hooks/useDefaultProps';
1414
import { cascaderDefaultProps } from './defaultProps';
@@ -41,6 +41,8 @@ const Cascader = forwardRef<HTMLDivElement, CascaderProps>((props) => {
4141
subTitles,
4242
options: inputOptions,
4343
keys,
44+
checkStrictly,
45+
closeBtn,
4446
onChange,
4547
onClose,
4648
onPick,
@@ -53,8 +55,7 @@ const Cascader = forwardRef<HTMLDivElement, CascaderProps>((props) => {
5355

5456
// 根据 inputOptions 和 key 重新构建 options
5557
const options = useMemo(() => {
56-
// TODO: keys 的类型 不对
57-
const { label = 'label', value = 'value', children = 'children' } = (keys || {}) as TreeKeysType;
58+
const { label = 'label', value = 'value', children = 'children' } = keys || {};
5859

5960
const convert = (options: TreeOptionData[]) =>
6061
options.map((item) => ({
@@ -115,9 +116,16 @@ const Cascader = forwardRef<HTMLDivElement, CascaderProps>((props) => {
115116
}, [optionsList, internalSelectedValues, placeholder]);
116117

117118
const selectedValuesByInterValue = useMemo(() => {
118-
// 最后一级的value为匹配时,返回整个链路上的value
119+
/**
120+
* checkStrictly true 从外到内 匹配上就挺 返回整个链路上的value
121+
* checkStrictly false 最后一级的 value 匹配时,返回整个链路上的value
122+
*/
119123
const findValues = (options: TreeOptionData[]): CascaderProps['value'][] => {
120124
for (const item of options) {
125+
if (checkStrictly && item.value === internalValue) {
126+
return [item.value];
127+
}
128+
121129
const isLast = !(Array.isArray(item.children) && item.children.length);
122130
if (isLast) {
123131
if (item.value === internalValue) {
@@ -134,7 +142,7 @@ const Cascader = forwardRef<HTMLDivElement, CascaderProps>((props) => {
134142
};
135143

136144
return findValues(options);
137-
}, [options, internalValue]);
145+
}, [options, internalValue, checkStrictly]);
138146

139147
// 当 selectedValuesByInterValue 深度变化 的时候再控制 selectedValues
140148
useDeepCompareEffect(() => {
@@ -149,6 +157,23 @@ const Cascader = forwardRef<HTMLDivElement, CascaderProps>((props) => {
149157
}
150158
}, [optionsList, stepIndex]);
151159

160+
// 结束了
161+
const onFinish = useCallback(
162+
(selectedValues: CascaderProps['value'][]) => {
163+
const selectedOptions = [...optionsList].slice(0, selectedValues.length).map((options, index) => {
164+
const target = options.find((item) => item.value === selectedValues[index]);
165+
const { label = 'label', value = 'value' } = keys || {};
166+
return {
167+
[label]: target?.label || '',
168+
[value]: target?.value || '',
169+
};
170+
});
171+
setInternalValue(last(selectedValues), selectedOptions as any);
172+
onClose?.('finish');
173+
},
174+
[onClose, optionsList, setInternalValue, keys],
175+
);
176+
152177
return (
153178
<Popup
154179
visible={internalVisible}
@@ -163,11 +188,16 @@ const Cascader = forwardRef<HTMLDivElement, CascaderProps>((props) => {
163188
<div
164189
className={`${cascaderClass}__close-btn`}
165190
onClick={() => {
191+
if (checkStrictly) {
192+
onFinish(internalSelectedValues);
193+
return;
194+
}
195+
166196
setInternalVisible(false);
167197
onClose?.('close-btn');
168198
}}
169199
>
170-
<Icon name="close" size={24} />
200+
{closeBtn === true ? <Icon name="close" size={24} /> : closeBtn}
171201
</div>
172202
<div className={`${cascaderClass}__content`}>
173203
{labelList.length ? (
@@ -248,23 +278,7 @@ const Cascader = forwardRef<HTMLDivElement, CascaderProps>((props) => {
248278
return;
249279
}
250280

251-
// 结束了
252-
const selectedOptions = optionsList.map((options, index) => {
253-
const target = options.find((item) => item.value === selectedValues[index]);
254-
const {
255-
label = 'label',
256-
value = 'value',
257-
children = 'children',
258-
} = (keys || {}) as TreeKeysType;
259-
return {
260-
[label]: target?.label || '',
261-
[value]: target?.value || '',
262-
[children]: target?.children,
263-
};
264-
});
265-
// TODO onChange 的 selectedOptions 类型不对
266-
setInternalValue(value, selectedOptions as any);
267-
onClose?.('finish');
281+
onFinish(selectedValues);
268282
}}
269283
>
270284
{curOptions.map((item) => (
+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import React, { useState } from 'react';
2+
3+
import { Cascader, Cell } from 'tdesign-mobile-react';
4+
import './style/index.less';
5+
6+
const data = {
7+
areaList: [
8+
{
9+
label: '北京市',
10+
value: '110000',
11+
children: [
12+
{
13+
value: '110100',
14+
label: '北京市',
15+
children: [
16+
{ value: '110101', label: '东城区' },
17+
{ value: '110102', label: '西城区' },
18+
{ value: '110105', label: '朝阳区' },
19+
{ value: '110106', label: '丰台区' },
20+
{ value: '110107', label: '石景山区' },
21+
{ value: '110108', label: '海淀区' },
22+
{ value: '110109', label: '门头沟区' },
23+
{ value: '110111', label: '房山区' },
24+
{ value: '110112', label: '通州区' },
25+
{ value: '110113', label: '顺义区' },
26+
{ value: '110114', label: '昌平区' },
27+
{ value: '110115', label: '大兴区' },
28+
{ value: '110116', label: '怀柔区' },
29+
{ value: '110117', label: '平谷区' },
30+
{ value: '110118', label: '密云区' },
31+
{ value: '110119', label: '延庆区' },
32+
],
33+
},
34+
],
35+
},
36+
{
37+
label: '天津市',
38+
value: '120000',
39+
children: [
40+
{
41+
value: '120100',
42+
label: '天津市',
43+
children: [
44+
{ value: '120101', label: '和平区' },
45+
{ value: '120102', label: '河东区' },
46+
{ value: '120103', label: '河西区' },
47+
{ value: '120104', label: '南开区' },
48+
{ value: '120105', label: '河北区' },
49+
{ value: '120106', label: '红桥区' },
50+
{ value: '120110', label: '东丽区' },
51+
{ value: '120111', label: '西青区' },
52+
{ value: '120112', label: '津南区' },
53+
{ value: '120113', label: '北辰区' },
54+
{ value: '120114', label: '武清区' },
55+
{ value: '120115', label: '宝坻区' },
56+
{ value: '120116', label: '滨海新区' },
57+
{ value: '120117', label: '宁河区' },
58+
{ value: '120118', label: '静海区' },
59+
{ value: '120119', label: '蓟州区' },
60+
],
61+
},
62+
],
63+
},
64+
],
65+
};
66+
67+
export default function CheckStrictlyDemo() {
68+
const [visible, setVisible] = useState(false);
69+
70+
const [note, setNote] = useState('请选择地址');
71+
72+
const [value, setValue] = useState<string | number | undefined>();
73+
74+
return (
75+
<>
76+
<Cell
77+
title="地址"
78+
note={note}
79+
arrow
80+
onClick={() => {
81+
setVisible(true);
82+
}}
83+
/>
84+
<Cascader
85+
title="选择地址"
86+
value={value}
87+
visible={visible}
88+
checkStrictly={true}
89+
closeBtn={<span style={{ color: '#0052d9' }}>确定</span>}
90+
options={data.areaList}
91+
onChange={(value, selectedOptions) => {
92+
setNote((selectedOptions as any).map((item) => item.label).join('/') || '');
93+
setValue(value);
94+
}}
95+
onClose={() => {
96+
setVisible(false);
97+
}}
98+
/>
99+
</>
100+
);
101+
}

src/cascader/_example/index.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import React from 'react';
22
import TDemoBlock from '../../../site/mobile/components/DemoBlock';
33
import TDemoHeader from '../../../site/mobile/components/DemoHeader';
44
import BaseDemo from './base';
5+
import CheckStrictlyDemo from './check-strictly';
56
import KeysDemo from './keys';
6-
import LazyDemo from './lazy';
77
import ThemeTabDemo from './theme-tab';
88
import WithTitleDemo from './with-title';
99
import WithValueDemo from './with-value';
@@ -29,8 +29,8 @@ export default function CascaderDemo() {
2929
<TDemoBlock title="" summary="使用次级标题">
3030
<WithTitleDemo />
3131
</TDemoBlock>
32-
<TDemoBlock title="" summary="异步加载">
33-
<LazyDemo />
32+
<TDemoBlock title="" summary="选择任意一项">
33+
<CheckStrictlyDemo />
3434
</TDemoBlock>
3535
</div>
3636
);

src/cascader/_example/lazy.tsx

-81
This file was deleted.

src/cascader/cascader.en-US.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@
22

33
## API
44

5-
65
### Cascader Props
76

87
name | type | default | description | required
98
-- | -- | -- | -- | --
109
className | String | - | className of component | N
1110
style | Object | - | CSS(Cascading Style Sheets),Typescript:`React.CSSProperties` | N
11+
checkStrictly | Boolean | false | \- | N
1212
closeBtn | TNode | true | Typescript:`boolean \| TNode`[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N
13-
keys | Object | - | Typescript:`KeysType`[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N
13+
keys | Object | - | Typescript:`CascaderKeysType` `type CascaderKeysType = TreeKeysType`[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts)[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/cascader/type.ts) | N
1414
lazy | Boolean | false | \- | N
1515
loadCompleted | Boolean | false | \- | N
1616
options | Array | [] | Typescript:`Array<CascaderOption>` | N
@@ -21,6 +21,6 @@ title | TNode | - | Typescript:`string \| TNode`。[see more ts definition](ht
2121
value | String / Number | - | \- | N
2222
defaultValue | String / Number | - | uncontrolled property | N
2323
visible | Boolean | false | \- | N
24-
onChange | Function | | Typescript:`(value: string \| number, selectedOptions: string[]) => void`<br/> | N
24+
onChange | Function | | Typescript:`(value: string \| number, selectedOptions: CascaderOption[]) => void`<br/> | N
2525
onClose | Function | | Typescript:`(trigger: TriggerSource) => void`<br/>[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/cascader/type.ts)。<br/>`type TriggerSource = 'overlay' \| 'close-btn' \| 'finish'`<br/> | N
2626
onPick | Function | | Typescript:`(value: string \| number, index: number) => void`<br/> | N

src/cascader/cascader.md

+5-5
Original file line numberDiff line numberDiff line change
@@ -36,22 +36,22 @@ toc: false
3636
::: demo _example/with-title
3737
:::
3838

39-
### 异步加载
39+
### 选择任意一项
4040

41-
::: demo _example/lazy
41+
::: demo _example/check-strictly
4242
:::
4343

4444
## API
4545

46-
4746
### Cascader Props
4847

4948
名称 | 类型 | 默认值 | 描述 | 必传
5049
-- | -- | -- | -- | --
5150
className | String | - | 类名 | N
5251
style | Object | - | 样式,TS 类型:`React.CSSProperties` | N
52+
checkStrictly | Boolean | false | 父子节点选中状态不再关联,可各自选中或取消 | N
5353
closeBtn | TNode | true | 关闭按钮。TS 类型:`boolean \| TNode`[通用类型定义](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N
54-
keys | Object | - | 用来定义 value / label 在 `options` 中对应的字段别名。TS 类型:`KeysType`[通用类型定义](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N
54+
keys | Object | - | 用来定义 value / label 在 `options` 中对应的字段别名。TS 类型:`CascaderKeysType` `type CascaderKeysType = TreeKeysType`[通用类型定义](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts)[详细类型定义](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/cascader/type.ts) | N
5555
lazy | Boolean | false | 是否异步加载 | N
5656
loadCompleted | Boolean | false | 是否完成异步加载 | N
5757
options | Array | [] | 可选项数据源。TS 类型:`Array<CascaderOption>` | N
@@ -62,6 +62,6 @@ title | TNode | - | 标题。TS 类型:`string \| TNode`。[通用类型定义
6262
value | String / Number | - | 选项值 | N
6363
defaultValue | String / Number | - | 选项值。非受控属性 | N
6464
visible | Boolean | false | 是否展示 | N
65-
onChange | Function | | TS 类型:`(value: string \| number, selectedOptions: string[]) => void`<br/>值发生变更时触发 | N
65+
onChange | Function | | TS 类型:`(value: string \| number, selectedOptions: CascaderOption[]) => void`<br/>值发生变更时触发 | N
6666
onClose | Function | | TS 类型:`(trigger: TriggerSource) => void`<br/>关闭时触发。[详细类型定义](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/cascader/type.ts)。<br/>`type TriggerSource = 'overlay' \| 'close-btn' \| 'finish'`<br/> | N
6767
onPick | Function | | TS 类型:`(value: string \| number, index: number) => void`<br/>选择后触发 | N

0 commit comments

Comments
 (0)