Skip to content

Commit

Permalink
feat(cascader): cascader
Browse files Browse the repository at this point in the history
  • Loading branch information
mingzhixu committed Aug 16, 2024
1 parent 3a541f7 commit 98cb7bb
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 118 deletions.
60 changes: 37 additions & 23 deletions src/cascader/Cascader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Popup } from 'tdesign-mobile-react/popup';
import { Radio, RadioGroup } from 'tdesign-mobile-react/radio';
import Tabs from 'tdesign-mobile-react/tabs';
import TabContext from 'tdesign-mobile-react/tabs/context';
import { StyledProps, TNode, TreeKeysType, TreeOptionData } from '../common';
import { StyledProps, TNode, TreeOptionData } from '../common';
import { usePrefixClass } from '../hooks/useClass';
import useDefaultProps from '../hooks/useDefaultProps';
import { cascaderDefaultProps } from './defaultProps';
Expand Down Expand Up @@ -41,6 +41,8 @@ const Cascader = forwardRef<HTMLDivElement, CascaderProps>((props) => {
subTitles,
options: inputOptions,
keys,
checkStrictly,
closeBtn,
onChange,
onClose,
onPick,
Expand All @@ -53,8 +55,7 @@ const Cascader = forwardRef<HTMLDivElement, CascaderProps>((props) => {

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

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

const selectedValuesByInterValue = useMemo(() => {
// 最后一级的value为匹配时,返回整个链路上的value
/**
* checkStrictly true 从外到内 匹配上就挺 返回整个链路上的value
* checkStrictly false 最后一级的 value 匹配时,返回整个链路上的value
*/
const findValues = (options: TreeOptionData[]): CascaderProps['value'][] => {
for (const item of options) {
if (checkStrictly && item.value === internalValue) {
return [item.value];
}

const isLast = !(Array.isArray(item.children) && item.children.length);
if (isLast) {
if (item.value === internalValue) {
Expand All @@ -134,7 +142,7 @@ const Cascader = forwardRef<HTMLDivElement, CascaderProps>((props) => {
};

return findValues(options);
}, [options, internalValue]);
}, [options, internalValue, checkStrictly]);

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

// 结束了
const onFinish = useCallback(
(selectedValues: CascaderProps['value'][]) => {
const selectedOptions = [...optionsList].slice(0, selectedValues.length).map((options, index) => {
const target = options.find((item) => item.value === selectedValues[index]);
const { label = 'label', value = 'value' } = keys || {};
return {
[label]: target?.label || '',
[value]: target?.value || '',
};
});
setInternalValue(last(selectedValues), selectedOptions as any);
onClose?.('finish');
},
[onClose, optionsList, setInternalValue, keys],
);

return (
<Popup
visible={internalVisible}
Expand All @@ -163,11 +188,16 @@ const Cascader = forwardRef<HTMLDivElement, CascaderProps>((props) => {
<div
className={`${cascaderClass}__close-btn`}
onClick={() => {
if (checkStrictly) {
onFinish(internalSelectedValues);
return;
}

setInternalVisible(false);
onClose?.('close-btn');
}}
>
<Icon name="close" size={24} />
{closeBtn === true ? <Icon name="close" size={24} /> : closeBtn}
</div>
<div className={`${cascaderClass}__content`}>
{labelList.length ? (
Expand Down Expand Up @@ -248,23 +278,7 @@ const Cascader = forwardRef<HTMLDivElement, CascaderProps>((props) => {
return;
}

// 结束了
const selectedOptions = optionsList.map((options, index) => {
const target = options.find((item) => item.value === selectedValues[index]);
const {
label = 'label',
value = 'value',
children = 'children',
} = (keys || {}) as TreeKeysType;
return {
[label]: target?.label || '',
[value]: target?.value || '',
[children]: target?.children,
};
});
// TODO onChange 的 selectedOptions 类型不对
setInternalValue(value, selectedOptions as any);
onClose?.('finish');
onFinish(selectedValues);
}}
>
{curOptions.map((item) => (
Expand Down
101 changes: 101 additions & 0 deletions src/cascader/_example/check-strictly.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import React, { useState } from 'react';

import { Cascader, Cell } from 'tdesign-mobile-react';
import './style/index.less';

const data = {
areaList: [
{
label: '北京市',
value: '110000',
children: [
{
value: '110100',
label: '北京市',
children: [
{ value: '110101', label: '东城区' },
{ value: '110102', label: '西城区' },
{ value: '110105', label: '朝阳区' },
{ value: '110106', label: '丰台区' },
{ value: '110107', label: '石景山区' },
{ value: '110108', label: '海淀区' },
{ value: '110109', label: '门头沟区' },
{ value: '110111', label: '房山区' },
{ value: '110112', label: '通州区' },
{ value: '110113', label: '顺义区' },
{ value: '110114', label: '昌平区' },
{ value: '110115', label: '大兴区' },
{ value: '110116', label: '怀柔区' },
{ value: '110117', label: '平谷区' },
{ value: '110118', label: '密云区' },
{ value: '110119', label: '延庆区' },
],
},
],
},
{
label: '天津市',
value: '120000',
children: [
{
value: '120100',
label: '天津市',
children: [
{ value: '120101', label: '和平区' },
{ value: '120102', label: '河东区' },
{ value: '120103', label: '河西区' },
{ value: '120104', label: '南开区' },
{ value: '120105', label: '河北区' },
{ value: '120106', label: '红桥区' },
{ value: '120110', label: '东丽区' },
{ value: '120111', label: '西青区' },
{ value: '120112', label: '津南区' },
{ value: '120113', label: '北辰区' },
{ value: '120114', label: '武清区' },
{ value: '120115', label: '宝坻区' },
{ value: '120116', label: '滨海新区' },
{ value: '120117', label: '宁河区' },
{ value: '120118', label: '静海区' },
{ value: '120119', label: '蓟州区' },
],
},
],
},
],
};

export default function CheckStrictlyDemo() {
const [visible, setVisible] = useState(false);

const [note, setNote] = useState('请选择地址');

const [value, setValue] = useState<string | number | undefined>();

return (
<>
<Cell
title="地址"
note={note}
arrow
onClick={() => {
setVisible(true);
}}
/>
<Cascader
title="选择地址"
value={value}
visible={visible}
checkStrictly={true}
closeBtn={<span style={{ color: '#0052d9' }}>确定</span>}
options={data.areaList}
onChange={(value, selectedOptions) => {
setNote((selectedOptions as any).map((item) => item.label).join('/') || '');
setValue(value);
}}
onClose={() => {
setVisible(false);
}}
/>
</>
);
}
6 changes: 3 additions & 3 deletions src/cascader/_example/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import React from 'react';
import TDemoBlock from '../../../site/mobile/components/DemoBlock';
import TDemoHeader from '../../../site/mobile/components/DemoHeader';
import BaseDemo from './base';
import CheckStrictlyDemo from './check-strictly';
import KeysDemo from './keys';
import LazyDemo from './lazy';
import ThemeTabDemo from './theme-tab';
import WithTitleDemo from './with-title';
import WithValueDemo from './with-value';
Expand All @@ -29,8 +29,8 @@ export default function CascaderDemo() {
<TDemoBlock title="" summary="使用次级标题">
<WithTitleDemo />
</TDemoBlock>
<TDemoBlock title="" summary="异步加载">
<LazyDemo />
<TDemoBlock title="" summary="选择任意一项">
<CheckStrictlyDemo />
</TDemoBlock>
</div>
);
Expand Down
81 changes: 0 additions & 81 deletions src/cascader/_example/lazy.tsx

This file was deleted.

6 changes: 3 additions & 3 deletions src/cascader/cascader.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@

## API


### Cascader Props

name | type | default | description | required
-- | -- | -- | -- | --
className | String | - | className of component | N
style | Object | - | CSS(Cascading Style Sheets),Typescript:`React.CSSProperties` | N
checkStrictly | Boolean | false | \- | N
closeBtn | TNode | true | Typescript:`boolean \| TNode`[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N
keys | Object | - | Typescript:`KeysType`[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N
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
lazy | Boolean | false | \- | N
loadCompleted | Boolean | false | \- | N
options | Array | [] | Typescript:`Array<CascaderOption>` | N
Expand All @@ -21,6 +21,6 @@ title | TNode | - | Typescript:`string \| TNode`。[see more ts definition](ht
value | String / Number | - | \- | N
defaultValue | String / Number | - | uncontrolled property | N
visible | Boolean | false | \- | N
onChange | Function | | Typescript:`(value: string \| number, selectedOptions: string[]) => void`<br/> | N
onChange | Function | | Typescript:`(value: string \| number, selectedOptions: CascaderOption[]) => void`<br/> | N
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
onPick | Function | | Typescript:`(value: string \| number, index: number) => void`<br/> | N
10 changes: 5 additions & 5 deletions src/cascader/cascader.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,22 +36,22 @@ toc: false
::: demo _example/with-title
:::

### 异步加载
### 选择任意一项

::: demo _example/lazy
::: demo _example/check-strictly
:::

## API


### Cascader Props

名称 | 类型 | 默认值 | 描述 | 必传
-- | -- | -- | -- | --
className | String | - | 类名 | N
style | Object | - | 样式,TS 类型:`React.CSSProperties` | N
checkStrictly | Boolean | false | 父子节点选中状态不再关联,可各自选中或取消 | N
closeBtn | TNode | true | 关闭按钮。TS 类型:`boolean \| TNode`[通用类型定义](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N
keys | Object | - | 用来定义 value / label 在 `options` 中对应的字段别名。TS 类型:`KeysType`[通用类型定义](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N
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
lazy | Boolean | false | 是否异步加载 | N
loadCompleted | Boolean | false | 是否完成异步加载 | N
options | Array | [] | 可选项数据源。TS 类型:`Array<CascaderOption>` | N
Expand All @@ -62,6 +62,6 @@ title | TNode | - | 标题。TS 类型:`string \| TNode`。[通用类型定义
value | String / Number | - | 选项值 | N
defaultValue | String / Number | - | 选项值。非受控属性 | N
visible | Boolean | false | 是否展示 | N
onChange | Function | | TS 类型:`(value: string \| number, selectedOptions: string[]) => void`<br/>值发生变更时触发 | N
onChange | Function | | TS 类型:`(value: string \| number, selectedOptions: CascaderOption[]) => void`<br/>值发生变更时触发 | N
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
onPick | Function | | TS 类型:`(value: string \| number, index: number) => void`<br/>选择后触发 | N
Loading

0 comments on commit 98cb7bb

Please sign in to comment.