@douyinfe/semi-ui 2.4.0 → 2.5.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 (95) hide show
  1. package/cascader/__test__/cascader.test.js +24 -0
  2. package/cascader/_story/cascader.stories.js +73 -0
  3. package/cascader/index.tsx +5 -2
  4. package/datePicker/_story/v2/FixInputRangeFocus.jsx +25 -0
  5. package/datePicker/_story/v2/index.js +2 -1
  6. package/datePicker/datePicker.tsx +4 -0
  7. package/dist/css/semi.css +51 -27
  8. package/dist/css/semi.min.css +1 -1
  9. package/dist/umd/semi-ui.js +481 -182
  10. package/dist/umd/semi-ui.js.map +1 -1
  11. package/dist/umd/semi-ui.min.js +1 -1
  12. package/dist/umd/semi-ui.min.js.map +1 -1
  13. package/form/_story/demo.jsx +1 -0
  14. package/input/index.tsx +1 -0
  15. package/input/textarea.tsx +1 -1
  16. package/lib/cjs/autoComplete/index.d.ts +1 -1
  17. package/lib/cjs/cascader/index.js +6 -0
  18. package/lib/cjs/datePicker/datePicker.js +16 -8
  19. package/lib/cjs/dropdown/index.d.ts +1 -1
  20. package/lib/cjs/form/baseForm.d.ts +1 -1
  21. package/lib/cjs/form/field.d.ts +1 -1
  22. package/lib/cjs/input/index.js +2 -1
  23. package/lib/cjs/input/textarea.js +1 -1
  24. package/lib/cjs/select/index.d.ts +3 -3
  25. package/lib/cjs/select/index.js +32 -28
  26. package/lib/cjs/select/option.js +2 -2
  27. package/lib/cjs/select/virtualRow.js +2 -2
  28. package/lib/cjs/table/Table.d.ts +1 -1
  29. package/lib/cjs/table/Table.js +8 -2
  30. package/lib/cjs/table/interface.d.ts +1 -0
  31. package/lib/cjs/tabs/interface.d.ts +1 -1
  32. package/lib/cjs/timePicker/TimePicker.js +2 -1
  33. package/lib/cjs/tooltip/index.d.ts +1 -1
  34. package/lib/cjs/tooltip/index.js +6 -2
  35. package/lib/cjs/tree/index.d.ts +2 -0
  36. package/lib/cjs/tree/index.js +15 -8
  37. package/lib/cjs/treeSelect/index.d.ts +2 -0
  38. package/lib/cjs/treeSelect/index.js +64 -27
  39. package/lib/cjs/upload/fileCard.js +31 -22
  40. package/lib/cjs/upload/index.d.ts +6 -0
  41. package/lib/cjs/upload/index.js +15 -8
  42. package/lib/cjs/upload/interface.d.ts +8 -6
  43. package/lib/es/autoComplete/index.d.ts +1 -1
  44. package/lib/es/cascader/index.js +5 -0
  45. package/lib/es/datePicker/datePicker.js +16 -8
  46. package/lib/es/dropdown/index.d.ts +1 -1
  47. package/lib/es/form/baseForm.d.ts +1 -1
  48. package/lib/es/form/field.d.ts +1 -1
  49. package/lib/es/input/index.js +2 -1
  50. package/lib/es/input/textarea.js +1 -1
  51. package/lib/es/select/index.d.ts +3 -3
  52. package/lib/es/select/index.js +30 -26
  53. package/lib/es/select/option.js +2 -2
  54. package/lib/es/select/virtualRow.js +2 -2
  55. package/lib/es/table/Table.d.ts +1 -1
  56. package/lib/es/table/Table.js +10 -2
  57. package/lib/es/table/interface.d.ts +1 -0
  58. package/lib/es/tabs/interface.d.ts +1 -1
  59. package/lib/es/timePicker/TimePicker.js +2 -1
  60. package/lib/es/tooltip/index.d.ts +1 -1
  61. package/lib/es/tooltip/index.js +6 -2
  62. package/lib/es/tree/index.d.ts +2 -0
  63. package/lib/es/tree/index.js +15 -8
  64. package/lib/es/treeSelect/index.d.ts +2 -0
  65. package/lib/es/treeSelect/index.js +64 -27
  66. package/lib/es/upload/fileCard.js +31 -24
  67. package/lib/es/upload/index.d.ts +6 -0
  68. package/lib/es/upload/index.js +14 -8
  69. package/lib/es/upload/interface.d.ts +8 -6
  70. package/package.json +9 -8
  71. package/select/index.tsx +18 -19
  72. package/select/option.tsx +2 -2
  73. package/select/virtualRow.tsx +2 -2
  74. package/table/Table.tsx +7 -2
  75. package/table/_story/table.stories.js +1 -2
  76. package/table/_story/v2/FixedHeaderMerge/index.jsx +98 -0
  77. package/table/_story/v2/FixedResizable/index.jsx +114 -0
  78. package/table/_story/v2/defaultFilteredValue.tsx +123 -0
  79. package/table/_story/v2/index.js +5 -0
  80. package/table/interface.ts +1 -0
  81. package/tabs/interface.ts +1 -1
  82. package/timePicker/TimePicker.tsx +1 -0
  83. package/tooltip/__test__/tooltip.test.js +48 -4
  84. package/tooltip/_story/tooltip.stories.js +83 -1
  85. package/tooltip/index.tsx +4 -4
  86. package/tree/__test__/treeMultiple.test.js +94 -0
  87. package/tree/_story/tree.stories.js +169 -0
  88. package/tree/index.tsx +12 -5
  89. package/treeSelect/__test__/treeMultiple.test.js +94 -0
  90. package/treeSelect/_story/treeSelect.stories.js +242 -0
  91. package/treeSelect/index.tsx +72 -40
  92. package/upload/_story/upload.stories.js +22 -6
  93. package/upload/fileCard.tsx +23 -23
  94. package/upload/index.tsx +15 -6
  95. package/upload/interface.ts +7 -5
@@ -84,22 +84,22 @@ export type RenderSelectedItemInMultiple = (
84
84
  export type RenderSelectedItem = RenderSelectedItemInSingle | RenderSelectedItemInMultiple;
85
85
 
86
86
  export type OverrideCommonProps =
87
- 'renderFullLabel'
88
- | 'renderLabel'
89
- | 'defaultValue'
90
- | 'emptyContent'
91
- | 'filterTreeNode'
92
- | 'style'
93
- | 'treeData'
94
- | 'value'
95
- | 'onExpand';
87
+ 'renderFullLabel'
88
+ | 'renderLabel'
89
+ | 'defaultValue'
90
+ | 'emptyContent'
91
+ | 'filterTreeNode'
92
+ | 'style'
93
+ | 'treeData'
94
+ | 'value'
95
+ | 'onExpand';
96
96
 
97
97
  /**
98
98
  * Type definition description:
99
99
  * TreeSelectProps inherits some properties from BasicTreeSelectProps (from foundation) and TreeProps (from semi-ui-react).
100
100
  */
101
101
  // eslint-disable-next-line max-len
102
- export interface TreeSelectProps extends Omit<BasicTreeSelectProps, OverrideCommonProps | 'validateStatus' | 'searchRender'>, Pick<TreeProps, OverrideCommonProps>{
102
+ export interface TreeSelectProps extends Omit<BasicTreeSelectProps, OverrideCommonProps | 'validateStatus' | 'searchRender'>, Pick<TreeProps, OverrideCommonProps> {
103
103
  'aria-describedby'?: React.AriaAttributes['aria-describedby'];
104
104
  'aria-errormessage'?: React.AriaAttributes['aria-errormessage'];
105
105
  'aria-invalid'?: React.AriaAttributes['aria-invalid'];
@@ -146,10 +146,10 @@ export interface TreeSelectProps extends Omit<BasicTreeSelectProps, OverrideComm
146
146
  }
147
147
 
148
148
  export type OverrideCommonState =
149
- 'keyEntities'
150
- | 'treeData'
151
- | 'disabledKeys'
152
- | 'flattenNodes';
149
+ 'keyEntities'
150
+ | 'treeData'
151
+ | 'disabledKeys'
152
+ | 'flattenNodes';
153
153
 
154
154
  // eslint-disable-next-line max-len
155
155
  export interface TreeSelectState extends Omit<BasicTreeSelectInnerData, OverrideCommonState | 'prevProps'>, Pick<TreeState, OverrideCommonState> {
@@ -242,7 +242,7 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
242
242
  outerBottomSlot: PropTypes.node,
243
243
  outerTopSlot: PropTypes.node,
244
244
  onVisibleChange: PropTypes.func,
245
- expandAction: PropTypes.oneOf(['click' as const, 'doubleClick' as const, false as const]),
245
+ expandAction: PropTypes.oneOf(['click' as const, 'doubleClick' as const, false as const]),
246
246
  searchPosition: PropTypes.oneOf([strings.SEARCH_POSITION_DROPDOWN, strings.SEARCH_POSITION_TRIGGER]),
247
247
  clickToHide: PropTypes.bool,
248
248
  renderLabel: PropTypes.func,
@@ -251,6 +251,7 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
251
251
  optionListStyle: PropTypes.object,
252
252
  searchRender: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]),
253
253
  renderSelectedItem: PropTypes.func,
254
+ checkRelation: PropTypes.string,
254
255
  'aria-label': PropTypes.string,
255
256
  };
256
257
 
@@ -280,6 +281,7 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
280
281
  expandAction: false,
281
282
  clickToHide: true,
282
283
  searchAutoFocus: false,
284
+ checkRelation: 'related',
283
285
  'aria-label': 'TreeSelect'
284
286
  };
285
287
  inputRef: React.RefObject<typeof Input>;
@@ -308,6 +310,7 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
308
310
  selectedKeys: [],
309
311
  checkedKeys: new Set(),
310
312
  halfCheckedKeys: new Set(),
313
+ realCheckedKeys: new Set([]),
311
314
  disabledKeys: new Set(),
312
315
  motionKeys: new Set([]),
313
316
  motionType: 'hide',
@@ -473,10 +476,14 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
473
476
  }
474
477
 
475
478
  if (checkedKeyValues) {
476
- const { checkedKeys, halfCheckedKeys } = calcCheckedKeys(checkedKeyValues, keyEntities);
479
+ if (props.checkRelation === 'unRelated') {
480
+ newState.realCheckedKeys = new Set(checkedKeyValues);
481
+ } else if (props.checkRelation === 'related') {
482
+ const { checkedKeys, halfCheckedKeys } = calcCheckedKeys(checkedKeyValues, keyEntities);
477
483
 
478
- newState.checkedKeys = checkedKeys;
479
- newState.halfCheckedKeys = halfCheckedKeys;
484
+ newState.checkedKeys = checkedKeys;
485
+ newState.halfCheckedKeys = halfCheckedKeys;
486
+ }
480
487
  }
481
488
  }
482
489
 
@@ -491,7 +498,7 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
491
498
  }
492
499
 
493
500
  // ================ disableStrictly =================
494
- if (treeData && props.disableStrictly) {
501
+ if (treeData && props.disableStrictly && props.checkRelation === 'related') {
495
502
  newState.disabledKeys = calcDisabledKeys(keyEntities);
496
503
  }
497
504
 
@@ -575,7 +582,7 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
575
582
  this.foundation.handleNodeLoad(loadedKeys, loadingKeys, data, resolve));
576
583
  },
577
584
  updateState: states => {
578
- this.setState({ ...states });
585
+ this.setState({ ...states } as TreeSelectState);
579
586
  },
580
587
  openMenu: () => {
581
588
  this.setState({ isOpen: true }, () => {
@@ -616,7 +623,7 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
616
623
  toggleHovering: bool => {
617
624
  this.setState({ isHovering: bool });
618
625
  },
619
- updateInputFocus: bool => {} // eslint-disable-line
626
+ updateInputFocus: bool => { } // eslint-disable-line
620
627
  };
621
628
  }
622
629
 
@@ -676,24 +683,39 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
676
683
  this.foundation.handleSelectionEnterPress(e);
677
684
  };
678
685
 
686
+ hasValue = (): boolean => {
687
+ const { multiple, checkRelation } = this.props;
688
+ const { realCheckedKeys, checkedKeys, selectedKeys } = this.state;
689
+ let hasValue = false;
690
+ if (multiple) {
691
+ if (checkRelation === 'related') {
692
+ hasValue = Boolean(checkedKeys.size);
693
+ } else if (checkRelation === 'unRelated') {
694
+ hasValue = Boolean(realCheckedKeys.size);
695
+ }
696
+ } else {
697
+ hasValue = Boolean(selectedKeys.length);
698
+ }
699
+ return hasValue;
700
+ }
701
+
679
702
  showClearBtn = () => {
680
- const { searchPosition } = this.props;
681
- const { inputValue } = this.state;
703
+ const { showClear, disabled, searchPosition } = this.props;
704
+ const { inputValue, isOpen, isHovering } = this.state;
682
705
  const triggerSearchHasInputValue = searchPosition === strings.SEARCH_POSITION_TRIGGER && inputValue;
683
- const { showClear, disabled, multiple } = this.props;
684
- const { selectedKeys, checkedKeys, isOpen, isHovering } = this.state;
685
- const hasValue = multiple ? Boolean(checkedKeys.size) : Boolean(selectedKeys.length);
686
- return showClear && (hasValue || triggerSearchHasInputValue) && !disabled && (isOpen || isHovering);
706
+
707
+ return showClear && (this.hasValue() || triggerSearchHasInputValue) && !disabled && (isOpen || isHovering);
687
708
  };
688
709
 
689
710
  renderTagList = () => {
690
- const { checkedKeys, keyEntities, disabledKeys } = this.state;
711
+ const { checkedKeys, keyEntities, disabledKeys, realCheckedKeys } = this.state;
691
712
  const {
692
713
  treeNodeLabelProp,
693
714
  leafOnly,
694
715
  disabled,
695
716
  disableStrictly,
696
717
  size,
718
+ checkRelation,
697
719
  renderSelectedItem: propRenderSelectedItem
698
720
  } = this.props;
699
721
  const renderSelectedItem = isFunction(propRenderSelectedItem) ?
@@ -702,7 +724,12 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
702
724
  isRenderInTag: true,
703
725
  content: get(item, treeNodeLabelProp, null)
704
726
  });
705
- const renderKeys = normalizeKeyList([...checkedKeys], keyEntities, leafOnly);
727
+ let renderKeys = [];
728
+ if (checkRelation === 'related') {
729
+ renderKeys = normalizeKeyList([...checkedKeys], keyEntities, leafOnly);
730
+ } else if (checkRelation === 'unRelated') {
731
+ renderKeys = [...realCheckedKeys];
732
+ }
706
733
  const tagList: Array<React.ReactNode> = [];
707
734
  // eslint-disable-next-line @typescript-eslint/no-shadow
708
735
  renderKeys.forEach((key: TreeNodeData['key']) => {
@@ -778,15 +805,13 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
778
805
  searchPosition,
779
806
  filterTreeNode,
780
807
  } = this.props;
781
- const { selectedKeys, checkedKeys } = this.state;
782
- const hasValue = multiple ? Boolean(checkedKeys.size) : Boolean(selectedKeys.length);
783
808
  const isTriggerPositionSearch = filterTreeNode && searchPosition === strings.SEARCH_POSITION_TRIGGER;
784
809
  // searchPosition = trigger
785
810
  if (isTriggerPositionSearch) {
786
811
  return multiple ? this.renderTagInput() : this.renderSingleTriggerSearch();
787
812
  }
788
813
  // searchPosition = dropdown and single seleciton
789
- if (!multiple || !hasValue) {
814
+ if (!multiple || !this.hasValue()) {
790
815
  const renderText = this.foundation.getRenderTextInSingle();
791
816
  const spanCls = cls({
792
817
  [`${prefixcls}-selection-placeholder`]: !renderText,
@@ -846,11 +871,11 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
846
871
  const clearCls = cls(`${prefixcls}-clearbtn`);
847
872
  if (showClearBtn) {
848
873
  return (
849
- <div
874
+ <div
850
875
  role='button'
851
- tabIndex={0}
852
- aria-label="Clear TreeSelect value"
853
- className={clearCls}
876
+ tabIndex={0}
877
+ aria-label="Clear TreeSelect value"
878
+ className={clearCls}
854
879
  onClick={this.handleClear}
855
880
  onKeyPress={this.handleClearEnterPress}
856
881
  >
@@ -962,7 +987,7 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
962
987
  onKeyPress={this.handleSelectionEnterPress}
963
988
  aria-invalid={this.props['aria-invalid']}
964
989
  aria-errormessage={this.props['aria-errormessage']}
965
- aria-label={this.props['aria-label']}
990
+ aria-label={this.props['aria-label']}
966
991
  aria-labelledby={this.props['aria-labelledby']}
967
992
  aria-describedby={this.props['aria-describedby']}
968
993
  aria-required={this.props['aria-required']}
@@ -1014,7 +1039,7 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
1014
1039
  });
1015
1040
  if (isFunction(renderSelectedItem)) {
1016
1041
  const { content, isRenderInTag } = treeNodeLabelProp in item && item ?
1017
- (renderSelectedItem as RenderSelectedItemInMultiple)(item, { index: idx, onClose }):
1042
+ (renderSelectedItem as RenderSelectedItemInMultiple)(item, { index: idx, onClose }) :
1018
1043
  null;
1019
1044
  if (isRenderInTag) {
1020
1045
  return <Tag {...tagProps}>{content}</Tag>;
@@ -1037,13 +1062,20 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
1037
1062
  searchAutoFocus,
1038
1063
  placeholder,
1039
1064
  maxTagCount,
1065
+ checkRelation,
1040
1066
  } = this.props;
1041
1067
  const {
1042
1068
  keyEntities,
1043
1069
  checkedKeys,
1044
- inputValue
1070
+ inputValue,
1071
+ realCheckedKeys,
1045
1072
  } = this.state;
1046
- const keyList = normalizeKeyList(checkedKeys, keyEntities, leafOnly);
1073
+ let keyList = [];
1074
+ if (checkRelation === 'related') {
1075
+ keyList = normalizeKeyList(checkedKeys, keyEntities, leafOnly);
1076
+ } else if (checkRelation === 'unRelated') {
1077
+ keyList = [...realCheckedKeys];
1078
+ }
1047
1079
  return (
1048
1080
  <TagInput
1049
1081
  maxTagCount={maxTagCount}
@@ -2,7 +2,7 @@
2
2
  import React, { useState } from 'react';
3
3
  import { Upload, Button, Toast, Icon } from '@douyinfe/semi-ui/index';
4
4
  import { withField, Form } from '../../form/index';
5
- import { IconPlus, IconFile, IconUpload } from '@douyinfe/semi-icons';
5
+ import { IconPlus, IconFile, IconUpload, IconEyeOpened, IconDownload, IconDelete } from '@douyinfe/semi-icons';
6
6
 
7
7
  import FileCard from '../fileCard';
8
8
 
@@ -50,7 +50,7 @@ let commonProps = {
50
50
  let url = fileItem.url;
51
51
  console.log(fileItem);
52
52
  window.open(url);
53
- },
53
+ }
54
54
  };
55
55
 
56
56
  export const BasicUsage = () => (
@@ -270,7 +270,7 @@ const defaultFileList = [
270
270
  'https://sf6-cdn-tos.douyinstatic.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/bf8647bffab13c38772c9ff94bf91a9d.jpg',
271
271
  },
272
272
  {
273
- uid: '5',
273
+ uid: '3',
274
274
  name: 'jiafang3.jpeg',
275
275
  status: 'uploading',
276
276
  percent: 50,
@@ -279,7 +279,7 @@ const defaultFileList = [
279
279
  'https://sf6-cdn-tos.douyinstatic.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/bf8647bffab13c38772c9ff94bf91a9d.jpg',
280
280
  },
281
281
  {
282
- uid: '5',
282
+ uid: '4',
283
283
  name: 'jiafang3.jpeg',
284
284
  status: 'validateFail',
285
285
  validateMessage: '文件过大',
@@ -288,7 +288,7 @@ const defaultFileList = [
288
288
  'https://sf6-cdn-tos.douyinstatic.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/bf8647bffab13c38772c9ff94bf91a9d.jpg',
289
289
  },
290
290
  {
291
- uid: '4',
291
+ uid: '5',
292
292
  name: 'jiafang4.jpeg',
293
293
  status: 'validating',
294
294
  validateMessage: '校验中',
@@ -399,11 +399,12 @@ PictureListType.story = {
399
399
  export const PictureListTypeWithDefaultFileList = () => (
400
400
  <>
401
401
  <Upload
402
- showReplace
403
402
  {...commonProps}
403
+ showReplace={false}
404
404
  action={action}
405
405
  listType="picture"
406
406
  accept="image/*"
407
+ renderPicPreviewIcon={()=><IconEyeOpened style={{color: 'var(--semi-color-white)',fontSize: 24}} />}
407
408
  defaultFileList={defaultFileList}
408
409
  >
409
410
  <React.Fragment>
@@ -941,3 +942,18 @@ export const _ForbiddenRemove = () => <ForbiddenRemove />;
941
942
  _ForbiddenRemove.story = {
942
943
  name: 'forbidden remove',
943
944
  };
945
+
946
+ export const CustomListOperation = () => {
947
+ const renderFileOperation = (fileItem)=>{
948
+ return <div style={{display: 'flex',columnGap: 8, padding: '0 8px'}}>
949
+ <Button icon={<IconEyeOpened></IconEyeOpened>} type="tertiary" theme="borderless" size="small"></Button>
950
+ <Button icon={<IconDownload></IconDownload>} type="tertiary" theme="borderless" size="small"></Button>
951
+ <Button onClick={e=>fileItem.onRemove()} icon={<IconDelete></IconDelete>} type="tertiary" theme="borderless" size="small"></Button>
952
+ </div>
953
+ }
954
+ return <Upload defaultFileList={defaultFileList} itemStyle={{width: 300}} renderFileOperation={renderFileOperation}><Button icon={<IconUpload />} theme="light">点击上传</Button></Upload>
955
+ }
956
+
957
+ CustomListOperation.story = {
958
+ name: 'custom list operation',
959
+ }
@@ -3,11 +3,11 @@ import cls from 'classnames';
3
3
  import PropTypes from 'prop-types';
4
4
  import { cssClasses, strings } from '@douyinfe/semi-foundation/upload/constants';
5
5
  import { getFileSize } from '@douyinfe/semi-foundation/upload/utils';
6
- import { IconAlertCircle, IconClose, IconFile, IconRefresh } from '@douyinfe/semi-icons';
6
+ import { IconAlertCircle, IconClose, IconClear, IconFile, IconRefresh, IconEyeOpened } from '@douyinfe/semi-icons';
7
7
  import LocaleConsumer from '../locale/localeConsumer';
8
8
  import { Locale } from '../locale/interface';
9
9
 
10
- import IconButton from '../iconButton/index';
10
+ import Button from '../button/index';
11
11
  import Progress from '../progress/index';
12
12
  import Tooltip from '../tooltip/index';
13
13
  import Spin from '../spin/index';
@@ -123,10 +123,11 @@ class FileCard extends PureComponent<FileCardProps> {
123
123
  }
124
124
 
125
125
  renderPic(locale: Locale['Upload']): ReactNode {
126
- const { url, percent, status, disabled, style, onPreviewClick, showPicInfo, renderPicInfo, renderThumbnail, name, index } = this.props;
126
+ const { url, percent, status, disabled, style, onPreviewClick, showPicInfo, renderPicInfo, renderPicPreviewIcon, renderThumbnail, name, index } = this.props;
127
127
  const showProgress = status === strings.FILE_STATUS_UPLOADING && percent !== 100;
128
128
  const showRetry = status === strings.FILE_STATUS_UPLOAD_FAIL && this.props.showRetry;
129
129
  const showReplace = status === strings.FILE_STATUS_SUCCESS && this.props.showReplace;
130
+ const showPreview = status === strings.FILE_STATUS_SUCCESS && !this.props.showReplace;
130
131
  const filePicCardCls = cls({
131
132
  [`${prefixCls}-picture-file-card`]: true,
132
133
  [`${prefixCls}-picture-file-card-disabled`]: disabled,
@@ -134,7 +135,6 @@ class FileCard extends PureComponent<FileCardProps> {
134
135
  [`${prefixCls}-picture-file-card-error`]: status === strings.FILE_STATUS_UPLOAD_FAIL,
135
136
  [`${prefixCls}-picture-file-card-uploading`]: showProgress
136
137
  });
137
- const closeCls = `${prefixCls}-picture-file-card-close`;
138
138
  const retry = (
139
139
  <div role="button" tabIndex={0} className={`${prefixCls}-picture-file-card-retry`} onClick={e => this.onRetry(e)}>
140
140
  <IconRefresh className={`${prefixCls}-picture-file-card-icon-retry`} />
@@ -146,7 +146,16 @@ class FileCard extends PureComponent<FileCardProps> {
146
146
  <ReplaceSvg className={`${prefixCls}-picture-file-card-icon-replace`} />
147
147
  </div>
148
148
  </Tooltip>
149
-
149
+ );
150
+ const preview = (
151
+ <div className={`${prefixCls}-picture-file-card-preview`}>
152
+ {typeof renderPicPreviewIcon === 'function'? renderPicPreviewIcon(this.props): null}
153
+ </div>
154
+ );
155
+ const close = (
156
+ <div role="button" tabIndex={0} className={`${prefixCls}-picture-file-card-close`} onClick={e => this.onRemove(e)}>
157
+ <IconClear className={`${prefixCls}-picture-file-card-icon-close`} />
158
+ </div>
150
159
  );
151
160
 
152
161
  const picInfo = typeof renderPicInfo === 'function' ? renderPicInfo(this.props) : (
@@ -161,19 +170,16 @@ class FileCard extends PureComponent<FileCardProps> {
161
170
  {showProgress ? <Progress percent={percent} type="circle" size="small" orbitStroke={'#FFF'} aria-label="uploading file progress" /> : null}
162
171
  {showRetry ? retry : null}
163
172
  {showReplace && replace}
173
+ {showPreview && preview}
164
174
  {showPicInfo && picInfo}
165
- {!disabled && (
166
- <div className={closeCls} onClick={e => this.onRemove(e)}>
167
- <IconClose tabIndex={0} role="button" size="extra-small" />
168
- </div>
169
- )}
175
+ {!disabled && close}
170
176
  {this.renderPicValidateMsg()}
171
177
  </div>
172
178
  );
173
179
  }
174
180
 
175
181
  renderFile(locale: Locale["Upload"]) {
176
- const { name, size, percent, url, showRetry: propsShowRetry, showReplace: propsShowReplace, preview, previewFile, status, style, onPreviewClick } = this.props;
182
+ const { name, size, percent, url, showRetry: propsShowRetry, showReplace: propsShowReplace, preview, previewFile, status, style, onPreviewClick, renderFileOperation } = this.props;
177
183
  const fileCardCls = cls({
178
184
  [`${prefixCls}-file-card`]: true,
179
185
  [`${prefixCls}-file-card-fail`]: status === strings.FILE_STATUS_VALID_FAIL || status === strings.FILE_STATUS_UPLOAD_FAIL,
@@ -195,6 +201,7 @@ class FileCard extends PureComponent<FileCardProps> {
195
201
  if (previewFile) {
196
202
  previewContent = previewFile(this.props);
197
203
  }
204
+ const operation = typeof renderFileOperation === 'function'? renderFileOperation(this.props) : <Button onClick={e => this.onRemove(e)} type="tertiary" icon={<IconClose />} theme="borderless" size="small" className={closeCls} />;
198
205
  return (
199
206
  <div role="listitem" className={fileCardCls} style={style} onClick={onPreviewClick}>
200
207
  <div className={previewCls}>
@@ -209,7 +216,7 @@ class FileCard extends PureComponent<FileCardProps> {
209
216
  <span className={`${infoCls}-size`}>{fileSize}</span>
210
217
  {showReplace && (
211
218
  <Tooltip trigger="hover" position="top" showArrow={false} content={locale.replace}>
212
- <IconButton
219
+ <Button
213
220
  onClick={e => this.onReplace(e)}
214
221
  type="tertiary"
215
222
  theme="borderless"
@@ -231,31 +238,24 @@ class FileCard extends PureComponent<FileCardProps> {
231
238
  {showRetry ? <span role="button" tabIndex={0} className={`${infoCls}-retry`} onClick={e => this.onRetry(e)}>{locale.retry}</span> : null}
232
239
  </div>
233
240
  </div>
234
- <IconButton
235
- onClick={e => this.onRemove(e)}
236
- type="tertiary"
237
- icon={<IconClose />}
238
- theme="borderless"
239
- size="small"
240
- className={closeCls}
241
- />
241
+ {operation}
242
242
  </div>
243
243
  );
244
244
  }
245
245
 
246
246
  onRemove(e: MouseEvent): void {
247
247
  e.stopPropagation();
248
- this.props.onRemove(this.props, e);
248
+ this.props.onRemove();
249
249
  }
250
250
 
251
251
  onReplace(e: MouseEvent): void {
252
252
  e.stopPropagation();
253
- this.props.onReplace(this.props, e);
253
+ this.props.onReplace();
254
254
  }
255
255
 
256
256
  onRetry(e: MouseEvent): void {
257
257
  e.stopPropagation();
258
- this.props.onRetry(this.props, e);
258
+ this.props.onRetry();
259
259
  }
260
260
 
261
261
  render() {
package/upload/index.tsx CHANGED
@@ -2,7 +2,7 @@
2
2
  import React, { ReactNode, CSSProperties, RefObject, ChangeEvent, DragEvent } from 'react';
3
3
  import cls from 'classnames';
4
4
  import PropTypes from 'prop-types';
5
- import { noop } from 'lodash';
5
+ import { noop, pick } from 'lodash';
6
6
  import UploadFoundation, { CustomFile, UploadAdapter, BeforeUploadObjectResult, AfterUploadResult } from '@douyinfe/semi-foundation/upload/foundation';
7
7
  import { strings, cssClasses } from '@douyinfe/semi-foundation/upload/constants';
8
8
  import FileCard from './fileCard';
@@ -39,6 +39,7 @@ export interface UploadProps {
39
39
  fileList?: Array<FileItem>;
40
40
  fileName?: string;
41
41
  headers?: Record<string, any> | ((file: File) => Record<string, string>);
42
+ hotSpotLocation?: 'start' | 'end';
42
43
  itemStyle?: CSSProperties;
43
44
  limit?: number;
44
45
  listType?: UploadListType;
@@ -66,6 +67,8 @@ export interface UploadProps {
66
67
  renderFileItem?: (renderFileItemProps: RenderFileItemProps) => ReactNode;
67
68
  renderPicInfo?: (renderFileItemProps: RenderFileItemProps) => ReactNode;
68
69
  renderThumbnail?: (renderFileItemProps: RenderFileItemProps) => ReactNode;
70
+ renderPicPreviewIcon?: (renderFileItemProps: RenderFileItemProps) => ReactNode;
71
+ renderFileOperation?: (fileItem: RenderFileItemProps) => ReactNode;
69
72
  showClear?: boolean;
70
73
  showPicInfo?: boolean; // Show pic info in picture wall
71
74
  showReplace?: boolean; // Display replacement function
@@ -111,6 +114,7 @@ class Upload extends BaseComponent<UploadProps, UploadState> {
111
114
  fileList: PropTypes.array, // files had been uploaded
112
115
  fileName: PropTypes.string, // same as name, to avoid props conflict in Form.Upload
113
116
  headers: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
117
+ hotSpotLocation: PropTypes.oneOf(['start','end']),
114
118
  itemStyle: PropTypes.object,
115
119
  limit: PropTypes.number, // 最大允许上传文件个数
116
120
  listType: PropTypes.oneOf<UploadProps['listType']>(strings.LIST_TYPE),
@@ -136,6 +140,8 @@ class Upload extends BaseComponent<UploadProps, UploadState> {
136
140
  prompt: PropTypes.node,
137
141
  promptPosition: PropTypes.oneOf<UploadProps['promptPosition']>(strings.PROMPT_POSITION),
138
142
  renderFileItem: PropTypes.func,
143
+ renderPicPreviewIcon: PropTypes.func,
144
+ renderFileOperation: PropTypes.func,
139
145
  renderPicInfo: PropTypes.func,
140
146
  renderThumbnail: PropTypes.func,
141
147
  showClear: PropTypes.bool,
@@ -156,6 +162,7 @@ class Upload extends BaseComponent<UploadProps, UploadState> {
156
162
  defaultFileList: [],
157
163
  disabled: false,
158
164
  listType: 'list' as const,
165
+ hotSpotLocation: 'end',
159
166
  multiple: false,
160
167
  onAcceptInvalid: noop,
161
168
  onChange: noop,
@@ -326,7 +333,7 @@ class Upload extends BaseComponent<UploadProps, UploadState> {
326
333
 
327
334
  renderFile = (file: FileItem, index: number, locale: Locale['Upload']): ReactNode => {
328
335
  const { name, status, validateMessage, _sizeInvalid, uid } = file;
329
- const { previewFile, listType, itemStyle, showRetry, showPicInfo, renderPicInfo, renderFileItem, renderThumbnail, disabled, onPreviewClick, showReplace } = this.props;
336
+ const { previewFile, listType, itemStyle, showPicInfo, renderPicInfo, renderPicPreviewIcon, renderFileOperation, renderFileItem, renderThumbnail, disabled, onPreviewClick } = this.props;
330
337
  const onRemove = (): void => this.remove(file);
331
338
  const onRetry = (): void => {
332
339
  this.foundation.retry(file);
@@ -335,6 +342,7 @@ class Upload extends BaseComponent<UploadProps, UploadState> {
335
342
  this.replace(index);
336
343
  };
337
344
  const fileCardProps = {
345
+ ...pick(this.props, ['showRetry', 'showReplace', '']),
338
346
  ...file,
339
347
  previewFile,
340
348
  listType,
@@ -342,13 +350,13 @@ class Upload extends BaseComponent<UploadProps, UploadState> {
342
350
  onRetry,
343
351
  index,
344
352
  key: uid || `${name}${index}`,
345
- showRetry: typeof file.showRetry !== 'undefined' ? file.showRetry : showRetry,
346
353
  style: itemStyle,
347
354
  disabled,
348
355
  showPicInfo,
349
356
  renderPicInfo,
357
+ renderPicPreviewIcon,
358
+ renderFileOperation,
350
359
  renderThumbnail,
351
- showReplace: typeof file.showReplace !== 'undefined' ? file.showReplace : showReplace,
352
360
  onReplace,
353
361
  onPreviewClick: typeof onPreviewClick !== 'undefined' ? (): void => this.foundation.handlePreviewClick(file) : undefined,
354
362
  };
@@ -382,7 +390,7 @@ class Upload extends BaseComponent<UploadProps, UploadState> {
382
390
  };
383
391
 
384
392
  renderFileListPic = () => {
385
- const { showUploadList, limit, disabled, children, draggable } = this.props;
393
+ const { showUploadList, limit, disabled, children, draggable, hotSpotLocation } = this.props;
386
394
  const { fileList: stateFileList, dragAreaStatus } = this.state;
387
395
  const fileList = this.props.fileList || stateFileList;
388
396
  const showAddTriggerInList = limit ? limit > fileList.length : true;
@@ -434,8 +442,9 @@ class Upload extends BaseComponent<UploadProps, UploadState> {
434
442
  {(locale: Locale['Upload']) => (
435
443
  <div {...containerProps}>
436
444
  <div className={mainCls} role="list" aria-label="picture list">
445
+ {showAddTriggerInList && hotSpotLocation === 'start' ? addContent : null}
437
446
  {fileList.map((file, index) => this.renderFile(file, index, locale))}
438
- {showAddTriggerInList ? addContent : null}
447
+ {showAddTriggerInList && hotSpotLocation === 'end' ? addContent : null}
439
448
  </div>
440
449
  </div>
441
450
  )}
@@ -48,14 +48,16 @@ export interface RenderFileItemProps extends FileItem {
48
48
  index?: number;
49
49
  previewFile?: (fileItem: RenderFileItemProps) => ReactNode;
50
50
  listType: UploadListType;
51
- onRemove: (props: RenderFileItemProps, e: MouseEvent) => void;
52
- onRetry: (props: RenderFileItemProps, e: MouseEvent) => void;
53
- onReplace: (props: RenderFileItemProps, e: MouseEvent) => void;
51
+ onRemove: () => void;
52
+ onRetry: () => void;
53
+ onReplace: () => void;
54
54
  key: string;
55
55
  showPicInfo?: boolean;
56
56
  renderPicInfo?: (renderFileItemProps: RenderFileItemProps) => ReactNode;
57
- showRetry: boolean;
58
- showReplace: boolean;
57
+ renderPicPreviewIcon?: (renderFileItemProps: RenderFileItemProps) => ReactNode;
58
+ renderFileOperation?: (fileItem: RenderFileItemProps) => ReactNode;
59
+ showRetry?: boolean;
60
+ showReplace?: boolean;
59
61
  style?: CSSProperties;
60
62
  disabled: boolean;
61
63
  onPreviewClick: () => void;