@douyinfe/semi-ui 2.17.0 → 2.19.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/anchor/index.tsx +1 -1
  2. package/anchor/link.tsx +3 -4
  3. package/autoComplete/__test__/autoComplete.test.js +6 -6
  4. package/autoComplete/index.tsx +3 -1
  5. package/autoComplete/option.tsx +164 -0
  6. package/calendar/__test__/calendar.test.js +21 -2
  7. package/calendar/_story/calendar.stories.js +31 -0
  8. package/calendar/index.tsx +3 -1
  9. package/calendar/interface.ts +2 -1
  10. package/carousel/index.tsx +5 -5
  11. package/checkbox/checkbox.tsx +10 -2
  12. package/dist/css/semi.css +174 -29
  13. package/dist/css/semi.min.css +1 -1
  14. package/dist/umd/semi-ui.js +4190 -5430
  15. package/dist/umd/semi-ui.js.map +1 -1
  16. package/dist/umd/semi-ui.min.js +1 -1
  17. package/dist/umd/semi-ui.min.js.map +1 -1
  18. package/form/_story/FieldProps/labelOptional.jsx +30 -0
  19. package/form/_story/form.stories.js +7 -0
  20. package/form/hoc/withField.tsx +1 -0
  21. package/form/label.tsx +21 -7
  22. package/gulpfile.js +3 -1
  23. package/lib/cjs/_base/base.css +35 -0
  24. package/lib/cjs/anchor/index.js +2 -1
  25. package/lib/cjs/anchor/link.d.ts +1 -1
  26. package/lib/cjs/anchor/link.js +9 -5
  27. package/lib/cjs/autoComplete/index.d.ts +1 -1
  28. package/lib/cjs/autoComplete/index.js +6 -3
  29. package/lib/cjs/autoComplete/option.d.ts +50 -0
  30. package/lib/cjs/autoComplete/option.js +218 -0
  31. package/lib/cjs/calendar/index.d.ts +2 -0
  32. package/lib/cjs/calendar/index.js +3 -1
  33. package/lib/cjs/calendar/interface.d.ts +2 -1
  34. package/lib/cjs/carousel/index.js +2 -2
  35. package/lib/cjs/checkbox/checkbox.d.ts +4 -0
  36. package/lib/cjs/checkbox/checkbox.js +9 -3
  37. package/lib/cjs/form/hoc/withField.js +2 -1
  38. package/lib/cjs/form/label.d.ts +8 -5
  39. package/lib/cjs/form/label.js +15 -4
  40. package/lib/cjs/locale/interface.d.ts +3 -0
  41. package/lib/cjs/locale/source/ar.js +3 -0
  42. package/lib/cjs/locale/source/de.js +3 -0
  43. package/lib/cjs/locale/source/en_GB.js +3 -0
  44. package/lib/cjs/locale/source/en_US.js +3 -0
  45. package/lib/cjs/locale/source/es.js +3 -0
  46. package/lib/cjs/locale/source/fr.js +3 -0
  47. package/lib/cjs/locale/source/id_ID.js +3 -0
  48. package/lib/cjs/locale/source/it.js +3 -0
  49. package/lib/cjs/locale/source/ja_JP.js +3 -0
  50. package/lib/cjs/locale/source/ko_KR.js +3 -0
  51. package/lib/cjs/locale/source/ms_MY.js +3 -0
  52. package/lib/cjs/locale/source/pt_BR.js +3 -0
  53. package/lib/cjs/locale/source/ru_RU.js +3 -0
  54. package/lib/cjs/locale/source/th_TH.js +3 -0
  55. package/lib/cjs/locale/source/tr_TR.js +3 -0
  56. package/lib/cjs/locale/source/vi_VN.js +3 -0
  57. package/lib/cjs/locale/source/zh_CN.js +3 -0
  58. package/lib/cjs/locale/source/zh_TW.js +3 -0
  59. package/lib/cjs/modal/Modal.js +0 -8
  60. package/lib/cjs/modal/ModalContent.js +4 -1
  61. package/lib/cjs/radio/radio.d.ts +2 -0
  62. package/lib/cjs/radio/radio.js +33 -8
  63. package/lib/cjs/table/ColumnFilter.js +4 -2
  64. package/lib/cjs/table/ColumnSorter.d.ts +1 -0
  65. package/lib/cjs/table/ColumnSorter.js +9 -6
  66. package/lib/cjs/table/Table.js +11 -4
  67. package/lib/cjs/tag/group.d.ts +3 -0
  68. package/lib/cjs/tag/group.js +24 -6
  69. package/lib/cjs/tag/index.d.ts +2 -1
  70. package/lib/cjs/tag/index.js +7 -5
  71. package/lib/cjs/tag/interface.d.ts +2 -1
  72. package/lib/cjs/tree/index.d.ts +3 -1
  73. package/lib/cjs/tree/index.js +23 -0
  74. package/lib/cjs/tree/interface.d.ts +4 -0
  75. package/lib/cjs/tree/treeNode.d.ts +4 -1
  76. package/lib/cjs/tree/treeNode.js +13 -4
  77. package/lib/es/_base/base.css +35 -0
  78. package/lib/es/anchor/index.js +2 -1
  79. package/lib/es/anchor/link.d.ts +1 -1
  80. package/lib/es/anchor/link.js +9 -5
  81. package/lib/es/autoComplete/index.d.ts +1 -1
  82. package/lib/es/autoComplete/index.js +6 -3
  83. package/lib/es/autoComplete/option.d.ts +50 -0
  84. package/lib/es/autoComplete/option.js +188 -0
  85. package/lib/es/calendar/index.d.ts +2 -0
  86. package/lib/es/calendar/index.js +3 -1
  87. package/lib/es/calendar/interface.d.ts +2 -1
  88. package/lib/es/carousel/index.js +2 -2
  89. package/lib/es/checkbox/checkbox.d.ts +4 -0
  90. package/lib/es/checkbox/checkbox.js +10 -4
  91. package/lib/es/form/hoc/withField.js +2 -1
  92. package/lib/es/form/label.d.ts +8 -5
  93. package/lib/es/form/label.js +13 -4
  94. package/lib/es/locale/interface.d.ts +3 -0
  95. package/lib/es/locale/source/ar.js +3 -0
  96. package/lib/es/locale/source/de.js +3 -0
  97. package/lib/es/locale/source/en_GB.js +3 -0
  98. package/lib/es/locale/source/en_US.js +3 -0
  99. package/lib/es/locale/source/es.js +3 -0
  100. package/lib/es/locale/source/fr.js +3 -0
  101. package/lib/es/locale/source/id_ID.js +3 -0
  102. package/lib/es/locale/source/it.js +3 -0
  103. package/lib/es/locale/source/ja_JP.js +3 -0
  104. package/lib/es/locale/source/ko_KR.js +3 -0
  105. package/lib/es/locale/source/ms_MY.js +3 -0
  106. package/lib/es/locale/source/pt_BR.js +3 -0
  107. package/lib/es/locale/source/ru_RU.js +3 -0
  108. package/lib/es/locale/source/th_TH.js +3 -0
  109. package/lib/es/locale/source/tr_TR.js +3 -0
  110. package/lib/es/locale/source/vi_VN.js +3 -0
  111. package/lib/es/locale/source/zh_CN.js +3 -0
  112. package/lib/es/locale/source/zh_TW.js +3 -0
  113. package/lib/es/modal/Modal.js +0 -8
  114. package/lib/es/modal/ModalContent.js +4 -1
  115. package/lib/es/radio/radio.d.ts +2 -0
  116. package/lib/es/radio/radio.js +31 -8
  117. package/lib/es/table/ColumnFilter.js +4 -2
  118. package/lib/es/table/ColumnSorter.d.ts +1 -0
  119. package/lib/es/table/ColumnSorter.js +9 -6
  120. package/lib/es/table/Table.js +10 -4
  121. package/lib/es/tag/group.d.ts +3 -0
  122. package/lib/es/tag/group.js +24 -6
  123. package/lib/es/tag/index.d.ts +2 -1
  124. package/lib/es/tag/index.js +7 -5
  125. package/lib/es/tag/interface.d.ts +2 -1
  126. package/lib/es/tree/index.d.ts +3 -1
  127. package/lib/es/tree/index.js +22 -0
  128. package/lib/es/tree/interface.d.ts +4 -0
  129. package/lib/es/tree/treeNode.d.ts +4 -1
  130. package/lib/es/tree/treeNode.js +13 -4
  131. package/locale/interface.ts +3 -0
  132. package/locale/source/ar.ts +3 -0
  133. package/locale/source/de.ts +3 -0
  134. package/locale/source/en_GB.ts +3 -0
  135. package/locale/source/en_US.ts +3 -0
  136. package/locale/source/es.ts +3 -0
  137. package/locale/source/fr.ts +3 -0
  138. package/locale/source/id_ID.ts +3 -0
  139. package/locale/source/it.ts +3 -0
  140. package/locale/source/ja_JP.ts +3 -0
  141. package/locale/source/ko_KR.ts +3 -0
  142. package/locale/source/ms_MY.ts +3 -0
  143. package/locale/source/pt_BR.ts +3 -0
  144. package/locale/source/ru_RU.ts +3 -0
  145. package/locale/source/th_TH.ts +3 -0
  146. package/locale/source/tr_TR.ts +4 -1
  147. package/locale/source/vi_VN.ts +3 -0
  148. package/locale/source/zh_CN.ts +3 -0
  149. package/locale/source/zh_TW.ts +3 -0
  150. package/modal/Modal.tsx +0 -6
  151. package/modal/ModalContent.tsx +4 -1
  152. package/modal/__test__/modal.test.js +1 -1
  153. package/modal/_story/__snapshots__/modal.stories.tsx.snap +203 -0
  154. package/package.json +7 -7
  155. package/radio/_story/radio.stories.js +2 -2
  156. package/radio/radio.tsx +27 -5
  157. package/rating/__test__/rating.test.js +1 -1
  158. package/select/__test__/select.test.js +11 -17
  159. package/select/_story/select.stories.js +6 -6
  160. package/steps/_story/steps.stories.js +3 -3
  161. package/switch/_story/switch.stories.js +4 -4
  162. package/switch/_story/switch.stories.tsx +4 -4
  163. package/table/ColumnFilter.tsx +2 -1
  164. package/table/ColumnSorter.tsx +16 -10
  165. package/table/Table.tsx +7 -4
  166. package/table/_story/v2/FixedFilter/index.tsx +106 -0
  167. package/table/_story/v2/FixedSorter/index.tsx +102 -0
  168. package/table/_story/v2/index.js +4 -2
  169. package/tag/_story/tag.stories.js +57 -1
  170. package/tag/group.tsx +20 -3
  171. package/tag/index.tsx +6 -5
  172. package/tag/interface.ts +2 -1
  173. package/transfer/_story/transfer.stories.js +2 -2
  174. package/tree/_story/tree.stories.js +152 -3
  175. package/tree/index.tsx +16 -1
  176. package/tree/interface.ts +6 -0
  177. package/tree/treeNode.tsx +11 -5
  178. package/upload/_story/upload.stories.js +2 -2
  179. package/webpack.config.js +13 -3
package/table/Table.tsx CHANGED
@@ -933,16 +933,22 @@ class Table<RecordType extends Record<string, any>> extends BaseComponent<Normal
933
933
  const stateSortOrder = get(curQuery, 'sortOrder');
934
934
  const defaultSortOrder = get(curQuery, 'defaultSortOrder', false);
935
935
  const sortOrder = this.foundation.isSortOrderValid(stateSortOrder) ? stateSortOrder : defaultSortOrder;
936
+ const TitleNode = typeof rawTitle !== 'function' && <React.Fragment key={strings.DEFAULT_KEY_COLUMN_TITLE}>{rawTitle as React.ReactNode}</React.Fragment>;
936
937
  if (typeof column.sorter === 'function' || column.sorter === true) {
938
+ // In order to increase the click hot area of ​​sorting, when sorting is required & useFullRender is false,
939
+ // both the title and sorting areas are used as the click hot area for sorting。
937
940
  const sorter = (
938
941
  <ColumnSorter
939
942
  key={strings.DEFAULT_KEY_COLUMN_SORTER}
940
943
  sortOrder={sortOrder}
941
944
  onClick={e => this.foundation.handleSort(column, e)}
945
+ title={TitleNode}
942
946
  />
943
947
  );
944
948
  useFullRender && (titleMap.sorter = sorter);
945
949
  titleArr.push(sorter);
950
+ } else {
951
+ titleArr.push(TitleNode);
946
952
  }
947
953
 
948
954
  const stateFilteredValue = get(curQuery, 'filteredValue');
@@ -964,10 +970,7 @@ class Table<RecordType extends Record<string, any>> extends BaseComponent<Normal
964
970
 
965
971
  const newTitle =
966
972
  typeof rawTitle === 'function' ?
967
- () => rawTitle(titleMap) :
968
- titleArr.unshift(
969
- <React.Fragment key={strings.DEFAULT_KEY_COLUMN_TITLE}>{rawTitle}</React.Fragment>
970
- ) && titleArr;
973
+ () => rawTitle(titleMap) : titleArr;
971
974
 
972
975
  column = { ...column, title: newTitle };
973
976
  }
@@ -0,0 +1,106 @@
1
+ import React, { useState, useMemo, useEffect } from 'react';
2
+
3
+ // eslint-disable-next-line semi-design/no-import
4
+ import { Table, Avatar } from '@douyinfe/semi-ui';
5
+ import * as dateFns from 'date-fns';
6
+
7
+ const DAY = 24 * 60 * 60 * 1000;
8
+ const figmaIconUrl = 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/figma-icon.png';
9
+
10
+ const columns = [
11
+ {
12
+ title: '标题',
13
+ dataIndex: 'name',
14
+ width: 400,
15
+ render: (text, record, index) => {
16
+ return (
17
+ <div>
18
+ <Avatar size="small" shape="square" src={figmaIconUrl} style={{ marginRight: 12 }}></Avatar>
19
+ {text}
20
+ </div>
21
+ );
22
+ },
23
+ filters: [
24
+ {
25
+ text: 'Semi Design 设计稿',
26
+ value: 'Semi Design 设计稿',
27
+ },
28
+ {
29
+ text: 'Semi Pro 设计稿',
30
+ value: 'Semi Pro 设计稿',
31
+ },
32
+ ],
33
+ onFilter: (value, record) => record.name.includes(value),
34
+ sorter: (a, b) => a.name.length - b.name.length > 0 ? 1 : -1,
35
+ },
36
+ {
37
+ title: '大小',
38
+ dataIndex: 'size',
39
+ sorter: (a, b) => a.size - b.size > 0 ? 1 : -1,
40
+ render: (text) => `${text} KB`
41
+ },
42
+ {
43
+ title: '所有者',
44
+ dataIndex: 'owner',
45
+ render: (text, record, index) => {
46
+ return (
47
+ <div>
48
+ <Avatar size="small" color={record.avatarBg} style={{ marginRight: 4 }}>{typeof text === 'string' && text.slice(0, 1)}</Avatar>
49
+ {text}
50
+ </div>
51
+ );
52
+ }
53
+
54
+ },
55
+ {
56
+ title: '更新日期',
57
+ dataIndex: 'updateTime',
58
+ sorter: (a, b) => a.updateTime - b.updateTime > 0 ? 1 : -1,
59
+ render: (value) => {
60
+ return dateFns.format(new Date(value), 'yyyy-MM-dd');
61
+ }
62
+ }
63
+ ];
64
+
65
+ App.storyName = 'Fixed filter issue 1036';
66
+ /**
67
+ * test with cypress, please don't modify this story
68
+ */
69
+ export default function App() {
70
+ const [dataSource, setData] = useState([]);
71
+
72
+ const rowSelection = useMemo(() => ({
73
+ onChange: (selectedRowKeys, selectedRows) => {
74
+ console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
75
+ },
76
+ getCheckboxProps: record => ({
77
+ disabled: record.name === 'Michael James', // Column configuration not to be checked
78
+ name: record.name,
79
+ }),
80
+ }), []);
81
+ const scroll = useMemo(() => ({ y: 300 }), []);
82
+
83
+ const getData = () => {
84
+ const data = [];
85
+ for (let i = 0; i < 46; i++) {
86
+ const isSemiDesign = i % 2 === 0;
87
+ const randomNumber = (i * 1000) % 199;
88
+ data.push({
89
+ key: '' + i,
90
+ name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi Pro 设计稿${i}.fig`,
91
+ owner: isSemiDesign ? '姜鹏志' : '郝宣',
92
+ size: randomNumber,
93
+ updateTime: new Date('2022-08-11').valueOf() + randomNumber * DAY,
94
+ avatarBg: isSemiDesign ? 'grey' : 'red'
95
+ });
96
+ }
97
+ return data;
98
+ };
99
+
100
+ useEffect(() => {
101
+ const data = getData();
102
+ setData(data);
103
+ }, []);
104
+
105
+ return <Table columns={columns} dataSource={dataSource} rowSelection={rowSelection} scroll={scroll} />;
106
+ }
@@ -0,0 +1,102 @@
1
+ import React, { useState } from 'react';
2
+ // eslint-disable-next-line semi-design/no-import
3
+ import { Table } from '@douyinfe/semi-ui';
4
+ // eslint-disable-next-line semi-design/no-import
5
+ import { ChangeInfo } from '@douyinfe/semi-ui/table';
6
+
7
+ const data = [
8
+ {
9
+ key: 'a',
10
+ group: 'yes',
11
+ count: 3,
12
+ },
13
+ {
14
+ key: 'b',
15
+ group: 'no',
16
+ count: 3,
17
+ },
18
+ {
19
+ key: 'c',
20
+ group: 'no',
21
+ count: 1,
22
+ },
23
+ {
24
+ key: 'd',
25
+ group: 'yes',
26
+ count: 1,
27
+ },
28
+ {
29
+ key: 'e',
30
+ group: 'no',
31
+ count: 2,
32
+ },
33
+ {
34
+ key: 'f',
35
+ group: 'yes',
36
+ count: 2,
37
+ }
38
+ ];
39
+
40
+ Demo.storyName = 'fixed sorter';
41
+ /**
42
+ * 保持分组顺序不变的排序方式
43
+ */
44
+ function Demo() {
45
+ const [filtered, setFiltered] = useState([...data]);
46
+ console.log(filtered);
47
+ const columns = [
48
+ {
49
+ title: 'ID',
50
+ dataIndex: 'key',
51
+ },
52
+ {
53
+ title: 'group',
54
+ dataIndex: 'Group',
55
+ sorter: (a, b) => a.group === 'yes' ? -1 : 1,
56
+ // sortOrder: 'ascend'
57
+ },
58
+ {
59
+ title: 'Count',
60
+ dataIndex: 'count',
61
+ // sorter: true
62
+ sorter: (a, b) => a.count - b.count > 0 ? 1 : -1,
63
+ },
64
+ ];
65
+
66
+ const onTableChange = ({ sorter }: ChangeInfo<any>) => {
67
+ if (sorter) {
68
+ const { dataIndex, sortOrder } = sorter;
69
+ setFiltered(prev => [...prev].sort((a, b) => {
70
+ if (a.group !== b.group) {
71
+ return a.group === 'yes' ? -1 : 1;
72
+ }
73
+
74
+ let ascendValue = -1;
75
+ if (dataIndex === 'count') {
76
+ ascendValue = a.count - b.count > 0 ? 1 : -1;
77
+ }
78
+
79
+ return sortOrder === 'ascend' ? ascendValue : -ascendValue;
80
+ }));
81
+ }
82
+ };
83
+
84
+
85
+ return (
86
+ <div style={{ padding: '20px 0px' }}>
87
+ <Table
88
+ dataSource={filtered}
89
+ onChange={onTableChange}
90
+ rowKey="key"
91
+ groupBy="group"
92
+ columns={columns}
93
+ renderGroupSection={groupKey => <strong>分组 {groupKey}</strong>}
94
+ expandAllGroupRows
95
+ scroll={{ y: 480 }}
96
+ pagination={{ pageSize: 4 }}
97
+ />
98
+ </div>
99
+ );
100
+ }
101
+
102
+ export default Demo;
@@ -4,7 +4,9 @@ export { default as FixedZIndex } from './FixedZIndex';
4
4
  export { default as FixedHeaderMerge } from './FixedHeaderMerge';
5
5
  export { default as FixedResizable } from './FixedResizable';
6
6
  export { default as FixedExpandedRow } from './FixedExpandedRow';
7
- export { default as FixedMemoryLeak } from './FixedMemoryLeak';
7
+ export { default as FixedMemoryLeak } from './FixedMemoryLeak';
8
8
  export { default as FixedOnHeaderRow } from './FixedOnHeaderRow';
9
9
  export { default as RadioRowSelection } from './radioRowSelection';
10
- export { default as FixedVirtualizedEmpty } from './FixedVirtualizedEmpty';
10
+ export { default as FixedVirtualizedEmpty } from './FixedVirtualizedEmpty';
11
+ export { default as FixedFilter } from './FixedFilter';
12
+ export { default as FixedSorter } from './FixedSorter';
@@ -1,5 +1,5 @@
1
1
  /* argus-disable unPkgSensitiveInfo */
2
- import React from 'react';
2
+ import React, { useCallback, useState } from 'react';
3
3
  import withPropsCombinations from 'react-storybook-addon-props-combinations';
4
4
  import { BASE_CLASS_PREFIX } from '../../../semi-foundation/base/constants';
5
5
 
@@ -232,3 +232,59 @@ export const AvatarTagGroup = () => <AvatarTagGroupDemo />;
232
232
  AvatarTagGroup.story = {
233
233
  name: 'avatar tagGroup',
234
234
  };
235
+
236
+ class TagGroupCloseableDemo extends React.Component {
237
+ constructor(props){
238
+ super(props);
239
+ this.state = {
240
+ tagList: [
241
+ { tagKey: '1', color: 'white', children: '抖音', closable: true,},
242
+ { tagKey: '2',color: 'white', children: '火山小视频', closable: true,},
243
+ { tagKey: '3',color: 'white', children: '剪映', closable: true,},
244
+ { tagKey: '4',color: 'white', children: '皮皮虾', closable: true,},
245
+ ]
246
+ };
247
+ this.tagListClick = this.tagListClick.bind(this);
248
+ }
249
+
250
+ tagListClick(value, e, tagKey){
251
+ const newTagList = [...this.state.tagList];
252
+ const closeTagIndex = newTagList.findIndex(t => t.tagKey === tagKey);
253
+ newTagList.splice(closeTagIndex, 1);
254
+ this.setState({
255
+ tagList: newTagList,
256
+ });
257
+ }
258
+
259
+ render() {
260
+ return (
261
+ <div style={ {
262
+ backgroundColor: 'var(--semi-color-fill-0)',
263
+ height: 35,
264
+ width: 300,
265
+ display: 'flex',
266
+ alignItems: 'center',
267
+ padding: '0 10px',
268
+ marginBottom: 30,
269
+ }}>
270
+ <TagGroup
271
+ maxTagCount={3}
272
+ style={ {
273
+ display: 'flex',
274
+ alignItems: 'center',
275
+ width: 350,
276
+ }}
277
+ tagList={this.state.tagList}
278
+ size='large'
279
+ onTagClose={this.tagListClick}
280
+ />
281
+ </div>
282
+ );
283
+ }
284
+ }
285
+
286
+ export const TagGroupCloseable = () => <TagGroupCloseableDemo />;
287
+
288
+ TagGroupCloseable.story = {
289
+ name: 'tagGroup closable',
290
+ }
package/tag/group.tsx CHANGED
@@ -21,6 +21,7 @@ export interface TagGroupProps<T> {
21
21
  popoverProps?: PopoverProps;
22
22
  avatarShape?: AvatarShape;
23
23
  mode?: string;
24
+ onTagClose: (tagChildren: React.ReactNode, event: React.MouseEvent<HTMLElement>, tagKey: string | number) => void;
24
25
  }
25
26
 
26
27
  export default class TagGroup<T> extends PureComponent<TagGroupProps<T>> {
@@ -29,6 +30,7 @@ export default class TagGroup<T> extends PureComponent<TagGroupProps<T>> {
29
30
  className: '',
30
31
  size: tagSize[0],
31
32
  avatarShape: 'square',
33
+ onTagClose: () => undefined,
32
34
  };
33
35
 
34
36
  static propTypes = {
@@ -40,6 +42,7 @@ export default class TagGroup<T> extends PureComponent<TagGroupProps<T>> {
40
42
  tagList: PropTypes.array,
41
43
  size: PropTypes.oneOf(tagSize),
42
44
  mode: PropTypes.string,
45
+ onTagClose: PropTypes.func,
43
46
  showPopover: PropTypes.bool,
44
47
  popoverProps: PropTypes.object,
45
48
  avatarShape: PropTypes.oneOf(avatarShapeSet),
@@ -95,18 +98,32 @@ export default class TagGroup<T> extends PureComponent<TagGroupProps<T>> {
95
98
  }
96
99
 
97
100
  renderAllTags() {
98
- const { tagList, size, mode, avatarShape } = this.props;
99
- const renderTags = tagList.map((tag, index): (Tag | React.ReactNode) => {
101
+ const { tagList, size, mode, avatarShape, onTagClose } = this.props;
102
+ const renderTags = tagList.map((tag): (Tag | React.ReactNode) => {
100
103
  if (mode === 'custom') {
101
104
  return tag as React.ReactNode;
102
105
  }
103
106
  if (!(tag as TagProps).size) {
104
107
  (tag as TagProps).size = size;
105
108
  }
109
+
106
110
  if (!(tag as TagProps).avatarShape) {
107
111
  (tag as TagProps).avatarShape = avatarShape;
108
112
  }
109
- return <Tag key={`${index}-tag`} {...(tag as TagProps)} />;
113
+
114
+ if (!(tag as TagProps).tagKey) {
115
+ if (typeof (tag as TagProps).children === 'string' || typeof (tag as TagProps).children === 'number') {
116
+ (tag as TagProps).tagKey = (tag as TagProps).children as string | number;
117
+ } else {
118
+ (tag as TagProps).tagKey = Math.random();
119
+ }
120
+ }
121
+ return <Tag {...(tag as TagProps)} key={(tag as TagProps).tagKey} onClose={(tagChildren, e, tagKey) => {
122
+ if ((tag as TagProps).onClose) {
123
+ (tag as TagProps).onClose(tagChildren, e, tagKey);
124
+ }
125
+ onTagClose && onTagClose(tagChildren, e, tagKey);
126
+ }} />;
110
127
  });
111
128
  return renderTags;
112
129
  }
package/tag/index.tsx CHANGED
@@ -40,6 +40,7 @@ export default class Tag extends Component<TagProps, TagState> {
40
40
 
41
41
  static propTypes = {
42
42
  children: PropTypes.node,
43
+ tagKey: PropTypes.oneOf([PropTypes.string, PropTypes.number]),
43
44
  size: PropTypes.oneOf(tagSize),
44
45
  color: PropTypes.oneOf(tagColors),
45
46
  type: PropTypes.oneOf(tagType),
@@ -79,11 +80,11 @@ export default class Tag extends Component<TagProps, TagState> {
79
80
  }
80
81
  }
81
82
 
82
- close(e: React.MouseEvent<HTMLElement>, value: React.ReactNode) {
83
+ close(e: React.MouseEvent<HTMLElement>, value: React.ReactNode, tagKey: string | number) {
83
84
  const { onClose } = this.props;
84
85
  e.stopPropagation();
85
86
  e.nativeEvent.stopImmediatePropagation();
86
- onClose && onClose(value, e);
87
+ onClose && onClose(value, e, tagKey);
87
88
  // when user call e.preventDefault() in onClick callback, tag will not hidden
88
89
  if (e.defaultPrevented) {
89
90
  return;
@@ -96,7 +97,7 @@ export default class Tag extends Component<TagProps, TagState> {
96
97
  switch (event.key) {
97
98
  case "Backspace":
98
99
  case "Delete":
99
- closable && this.close(event, this.props.children);
100
+ closable && this.close(event, this.props.children, this.props.tagKey);
100
101
  handlePrevent(event);
101
102
  break;
102
103
  case "Enter":
@@ -119,7 +120,7 @@ export default class Tag extends Component<TagProps, TagState> {
119
120
  }
120
121
 
121
122
  render() {
122
- const { children, size, color, closable, visible, onClose, onClick, className, type, avatarSrc, avatarShape, tabIndex, ...attr } = this.props;
123
+ const { tagKey, children, size, color, closable, visible, onClose, onClick, className, type, avatarSrc, avatarShape, tabIndex, ...attr } = this.props;
123
124
  const { visible: isVisible } = this.state;
124
125
  const clickable = onClick !== Tag.defaultProps.onClick || closable;
125
126
  // only when the Tag is clickable or closable, the value of tabIndex is allowed to be passed in.
@@ -145,7 +146,7 @@ export default class Tag extends Component<TagProps, TagState> {
145
146
  const wrapProps = clickable ? ({ ...baseProps, ...a11yProps }) : baseProps;
146
147
  const closeIcon = closable ? (
147
148
  // eslint-disable-next-line jsx-a11y/click-events-have-key-events
148
- <div className={`${prefixCls}-close`} onClick={e => this.close(e, children)}>
149
+ <div className={`${prefixCls}-close`} onClick={e => this.close(e, children, tagKey)}>
149
150
  <IconClose size="small" />
150
151
  </div>
151
152
  ) : null;
package/tag/interface.ts CHANGED
@@ -22,12 +22,13 @@ export type AvatarShape = 'circle' | 'square';
22
22
 
23
23
  export interface TagProps {
24
24
  children?: React.ReactNode;
25
+ tagKey?: string | number;
25
26
  size?: TagSize;
26
27
  color?: TagColor;
27
28
  type?: TagType;
28
29
  closable?: boolean;
29
30
  visible?: boolean;
30
- onClose?: (tagChildren: React.ReactNode, event: React.MouseEvent<HTMLElement>) => void;
31
+ onClose?: (tagChildren: React.ReactNode, event: React.MouseEvent<HTMLElement>, tagKey: string | number) => void;
31
32
  onClick?: React.MouseEventHandler<HTMLDivElement>;
32
33
  style?: React.CSSProperties;
33
34
  className?: string;
@@ -195,7 +195,7 @@ TransferDraggableAndDisabled.story = {
195
195
  }
196
196
 
197
197
 
198
- const ControledTransfer = () => {
198
+ const ControlledTransfer = () => {
199
199
  const [value, setValue] = useState([2, 3]);
200
200
 
201
201
  const handleChange = value => {
@@ -209,7 +209,7 @@ const ControledTransfer = () => {
209
209
  );
210
210
  };
211
211
 
212
- export const ControlledTransfer = () => <ControledTransfer />;
212
+ export const ControlledTransfer = () => <ControlledTransfer />;
213
213
 
214
214
  ControlledTransfer.story = {
215
215
  name: '受控Transfer',
@@ -1,9 +1,9 @@
1
- import React, { useRef, useState } from 'react';
1
+ import React, { useRef, useState, useCallback } from 'react';
2
2
  import { cloneDeep, difference, isEqual } from 'lodash';
3
3
  import { IconEdit, IconMapPin, IconMore } from '@douyinfe/semi-icons';
4
4
  import Tree from '../index';
5
5
  import AutoSizer from '../autoSizer';
6
- import { Button, ButtonGroup, Input, Popover, Toast, Space } from '../../index';
6
+ import { Button, ButtonGroup, Input, Popover, Toast, Space, Select, Switch } from '../../index';
7
7
  import BigTree from './BigData';
8
8
  import testData from './data';
9
9
  const TreeNode = Tree.TreeNode;
@@ -2398,4 +2398,153 @@ export const ValueImpactExpansionWithDynamicTreeData = () => {
2398
2398
  </Space>
2399
2399
  </>
2400
2400
  )
2401
- }
2401
+ }
2402
+
2403
+ class DemoV extends React.Component {
2404
+ constructor() {
2405
+ super();
2406
+ this.state = {
2407
+ gData: [],
2408
+ total: 0,
2409
+ align: 'center',
2410
+ scrollKey: '',
2411
+ expandAll: false,
2412
+ };
2413
+ this.onGen = this.onGen.bind(this);
2414
+ this.onScroll = this.onScroll.bind(this);
2415
+ this.onInputChange = this.onInputChange.bind(this);
2416
+ this.onInputBlur = this.onInputBlur.bind(this);
2417
+ this.onSelectChange = this.onSelectChange.bind(this);
2418
+ this.treeRef = React.createRef();
2419
+ }
2420
+
2421
+ generateData(x = 5, y = 4, z = 3, gData = []) {
2422
+ // x:每一级下的节点总数。y:每级节点里有y个节点、存在子节点。z:树的level层级数(0表示一级)
2423
+ function _loop(_level, _preKey, _tns) {
2424
+ const preKey = _preKey || '0';
2425
+ const tns = _tns || gData;
2426
+
2427
+ const children = [];
2428
+ for (let i = 0; i < x; i++) {
2429
+ const key = `${preKey}-${i}`;
2430
+ tns.push({ label: `${key}-标签`, key: `${key}-key`, value: `${key}-value` });
2431
+ if (i < y) {
2432
+ children.push(key);
2433
+ }
2434
+ }
2435
+ if (_level < 0) {
2436
+ return tns;
2437
+ }
2438
+ const __level = _level - 1;
2439
+ children.forEach((key, index) => {
2440
+ tns[index].children = [];
2441
+ return _loop(__level, key, tns[index].children);
2442
+ });
2443
+
2444
+ return null;
2445
+ }
2446
+ _loop(z);
2447
+
2448
+ function calcTotal(x, y, z) {
2449
+ const rec = n => (n >= 0 ? x * y ** n-- + rec(n) : 0);
2450
+ return rec(z + 1);
2451
+ }
2452
+ return { gData, total: calcTotal(x, y, z) };
2453
+ }
2454
+
2455
+ onGen() {
2456
+ const { gData, total } = this.generateData();
2457
+ this.setState({
2458
+ gData,
2459
+ total
2460
+ });
2461
+ };
2462
+
2463
+ onScroll(scrollKey, align) {
2464
+ this.treeRef?.current.scrollTo({ key: scrollKey, align});
2465
+ }
2466
+
2467
+ onInputChange(value) {
2468
+ this.setState({
2469
+ scrollKey: value,
2470
+ })
2471
+ }
2472
+
2473
+ onInputBlur(e) {
2474
+ const { value } = e.target;
2475
+ this.onScroll(value, this.state.align);
2476
+ }
2477
+
2478
+ onSelectChange(align){
2479
+ this.setState({
2480
+ align: align,
2481
+ })
2482
+ this.onScroll(this.state.scrollKey, align);
2483
+ }
2484
+
2485
+ render() {
2486
+ const style = {
2487
+ width: 260,
2488
+ border: '1px solid var(--semi-color-border)'
2489
+ };
2490
+ return (
2491
+ <div style={{ padding: '0 20px' }}>
2492
+ <Button onClick={this.onGen}>生成数据: </Button>
2493
+ <span>共 {this.state.total} 个节点</span>
2494
+ <br/>
2495
+ <br/>
2496
+ <div style={{ display: 'flex', alignItems: 'center', }}>
2497
+ <span>defaultExpandAll</span>
2498
+ <Switch onChange={(value) => {
2499
+ this.setState({
2500
+ expandAll: value,
2501
+ })
2502
+ }}/>
2503
+ </div>
2504
+ <br/>
2505
+ <span>跳转的key:</span>
2506
+ <Input
2507
+ placeholder={'格式:x-x-key'}
2508
+ style={{ width: 180, marginRight: 20 }}
2509
+ onChange={this.onInputChange}
2510
+ onBlur={this.onInputBlur}
2511
+ ></Input>
2512
+ <span>scroll align:</span>
2513
+ <Select
2514
+ defaultValue='center'
2515
+ style={{ width: 180 }}
2516
+ optionList={['center', 'start', 'end', 'smart', 'auto'].map(item => ({
2517
+ value: item,
2518
+ label: item,
2519
+ }))}
2520
+ onChange={this.onSelectChange}
2521
+ >
2522
+ </Select>
2523
+ <br />
2524
+ <br />
2525
+ {this.state.gData.length ? (
2526
+ <Tree
2527
+ key={`key-${this.state.expandAll}`}
2528
+ ref={this.treeRef}
2529
+ defaultExpandAll={this.state.expandAll}
2530
+ treeData={this.state.gData}
2531
+ filterTreeNode
2532
+ showFilteredOnly
2533
+ style={style}
2534
+ virtualize={{
2535
+ // if set height for tree, it will fill 100%
2536
+ height: 300,
2537
+ itemSize: 28,
2538
+ }}
2539
+ />
2540
+ ) : null}
2541
+ </div>
2542
+ );
2543
+ }
2544
+ }
2545
+
2546
+ export const virtualizeTree = () => <DemoV />;
2547
+
2548
+ virtualizeTree.story = {
2549
+ name: 'virtualize tree',
2550
+ };
package/tree/index.tsx CHANGED
@@ -39,7 +39,8 @@ import {
39
39
  TreeNodeData,
40
40
  FlattenNode,
41
41
  KeyEntity,
42
- OptionProps
42
+ OptionProps,
43
+ ScrollData,
43
44
  } from './interface';
44
45
  import CheckboxGroup from '../checkbox/checkboxGroup';
45
46
 
@@ -146,6 +147,7 @@ class Tree extends BaseComponent<TreeProps, TreeState> {
146
147
  onNodeClick: any;
147
148
  onMotionEnd: any;
148
149
  context: ContextValue;
150
+ virtualizedListRef: React.RefObject<any>;
149
151
 
150
152
  constructor(props: TreeProps) {
151
153
  super(props);
@@ -179,6 +181,7 @@ class Tree extends BaseComponent<TreeProps, TreeState> {
179
181
  this.optionsRef = React.createRef();
180
182
  this.foundation = new TreeFoundation(this.adapter);
181
183
  this.dragNode = null;
184
+ this.virtualizedListRef = React.createRef();
182
185
  }
183
186
 
184
187
  /**
@@ -493,6 +496,17 @@ class Tree extends BaseComponent<TreeProps, TreeState> {
493
496
  this.foundation.handleInputChange(value);
494
497
  };
495
498
 
499
+ scrollTo = (scrollData: ScrollData) => {
500
+ const { key, align = 'center' } = scrollData;
501
+ const { flattenNodes } = this.state;
502
+ if (key) {
503
+ const index = flattenNodes?.findIndex((node) => {
504
+ return node.key === key;
505
+ });
506
+ index >= 0 && (this.virtualizedListRef.current as any)?.scrollToItem(index, align);
507
+ }
508
+ }
509
+
496
510
  renderInput() {
497
511
  const {
498
512
  searchClassName,
@@ -664,6 +678,7 @@ class Tree extends BaseComponent<TreeProps, TreeState> {
664
678
  <AutoSizer defaultHeight={virtualize.height} defaultWidth={virtualize.width}>
665
679
  {({ height, width }: { width: string | number; height: string | number }) => (
666
680
  <VirtualList
681
+ ref={this.virtualizedListRef}
667
682
  itemCount={flattenNodes.length}
668
683
  itemSize={virtualize.itemSize}
669
684
  height={height}