@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.
- package/cascader/__test__/cascader.test.js +24 -0
- package/cascader/_story/cascader.stories.js +73 -0
- package/cascader/index.tsx +5 -2
- package/datePicker/_story/v2/FixInputRangeFocus.jsx +25 -0
- package/datePicker/_story/v2/index.js +2 -1
- package/datePicker/datePicker.tsx +4 -0
- package/dist/css/semi.css +51 -27
- package/dist/css/semi.min.css +1 -1
- package/dist/umd/semi-ui.js +481 -182
- package/dist/umd/semi-ui.js.map +1 -1
- package/dist/umd/semi-ui.min.js +1 -1
- package/dist/umd/semi-ui.min.js.map +1 -1
- package/form/_story/demo.jsx +1 -0
- package/input/index.tsx +1 -0
- package/input/textarea.tsx +1 -1
- package/lib/cjs/autoComplete/index.d.ts +1 -1
- package/lib/cjs/cascader/index.js +6 -0
- package/lib/cjs/datePicker/datePicker.js +16 -8
- package/lib/cjs/dropdown/index.d.ts +1 -1
- package/lib/cjs/form/baseForm.d.ts +1 -1
- package/lib/cjs/form/field.d.ts +1 -1
- package/lib/cjs/input/index.js +2 -1
- package/lib/cjs/input/textarea.js +1 -1
- package/lib/cjs/select/index.d.ts +3 -3
- package/lib/cjs/select/index.js +32 -28
- package/lib/cjs/select/option.js +2 -2
- package/lib/cjs/select/virtualRow.js +2 -2
- package/lib/cjs/table/Table.d.ts +1 -1
- package/lib/cjs/table/Table.js +8 -2
- package/lib/cjs/table/interface.d.ts +1 -0
- package/lib/cjs/tabs/interface.d.ts +1 -1
- package/lib/cjs/timePicker/TimePicker.js +2 -1
- package/lib/cjs/tooltip/index.d.ts +1 -1
- package/lib/cjs/tooltip/index.js +6 -2
- package/lib/cjs/tree/index.d.ts +2 -0
- package/lib/cjs/tree/index.js +15 -8
- package/lib/cjs/treeSelect/index.d.ts +2 -0
- package/lib/cjs/treeSelect/index.js +64 -27
- package/lib/cjs/upload/fileCard.js +31 -22
- package/lib/cjs/upload/index.d.ts +6 -0
- package/lib/cjs/upload/index.js +15 -8
- package/lib/cjs/upload/interface.d.ts +8 -6
- package/lib/es/autoComplete/index.d.ts +1 -1
- package/lib/es/cascader/index.js +5 -0
- package/lib/es/datePicker/datePicker.js +16 -8
- package/lib/es/dropdown/index.d.ts +1 -1
- package/lib/es/form/baseForm.d.ts +1 -1
- package/lib/es/form/field.d.ts +1 -1
- package/lib/es/input/index.js +2 -1
- package/lib/es/input/textarea.js +1 -1
- package/lib/es/select/index.d.ts +3 -3
- package/lib/es/select/index.js +30 -26
- package/lib/es/select/option.js +2 -2
- package/lib/es/select/virtualRow.js +2 -2
- package/lib/es/table/Table.d.ts +1 -1
- package/lib/es/table/Table.js +10 -2
- package/lib/es/table/interface.d.ts +1 -0
- package/lib/es/tabs/interface.d.ts +1 -1
- package/lib/es/timePicker/TimePicker.js +2 -1
- package/lib/es/tooltip/index.d.ts +1 -1
- package/lib/es/tooltip/index.js +6 -2
- package/lib/es/tree/index.d.ts +2 -0
- package/lib/es/tree/index.js +15 -8
- package/lib/es/treeSelect/index.d.ts +2 -0
- package/lib/es/treeSelect/index.js +64 -27
- package/lib/es/upload/fileCard.js +31 -24
- package/lib/es/upload/index.d.ts +6 -0
- package/lib/es/upload/index.js +14 -8
- package/lib/es/upload/interface.d.ts +8 -6
- package/package.json +9 -8
- package/select/index.tsx +18 -19
- package/select/option.tsx +2 -2
- package/select/virtualRow.tsx +2 -2
- package/table/Table.tsx +7 -2
- package/table/_story/table.stories.js +1 -2
- package/table/_story/v2/FixedHeaderMerge/index.jsx +98 -0
- package/table/_story/v2/FixedResizable/index.jsx +114 -0
- package/table/_story/v2/defaultFilteredValue.tsx +123 -0
- package/table/_story/v2/index.js +5 -0
- package/table/interface.ts +1 -0
- package/tabs/interface.ts +1 -1
- package/timePicker/TimePicker.tsx +1 -0
- package/tooltip/__test__/tooltip.test.js +48 -4
- package/tooltip/_story/tooltip.stories.js +83 -1
- package/tooltip/index.tsx +4 -4
- package/tree/__test__/treeMultiple.test.js +94 -0
- package/tree/_story/tree.stories.js +169 -0
- package/tree/index.tsx +12 -5
- package/treeSelect/__test__/treeMultiple.test.js +94 -0
- package/treeSelect/_story/treeSelect.stories.js +242 -0
- package/treeSelect/index.tsx +72 -40
- package/upload/_story/upload.stories.js +22 -6
- package/upload/fileCard.tsx +23 -23
- package/upload/index.tsx +15 -6
- package/upload/interface.ts +7 -5
package/treeSelect/index.tsx
CHANGED
|
@@ -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'
|
|
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
|
-
|
|
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
|
-
|
|
479
|
-
|
|
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
|
-
|
|
684
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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
|
+
}
|
package/upload/fileCard.tsx
CHANGED
|
@@ -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
|
|
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
|
-
<
|
|
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
|
-
|
|
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(
|
|
248
|
+
this.props.onRemove();
|
|
249
249
|
}
|
|
250
250
|
|
|
251
251
|
onReplace(e: MouseEvent): void {
|
|
252
252
|
e.stopPropagation();
|
|
253
|
-
this.props.onReplace(
|
|
253
|
+
this.props.onReplace();
|
|
254
254
|
}
|
|
255
255
|
|
|
256
256
|
onRetry(e: MouseEvent): void {
|
|
257
257
|
e.stopPropagation();
|
|
258
|
-
this.props.onRetry(
|
|
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,
|
|
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
|
)}
|
package/upload/interface.ts
CHANGED
|
@@ -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: (
|
|
52
|
-
onRetry: (
|
|
53
|
-
onReplace: (
|
|
51
|
+
onRemove: () => void;
|
|
52
|
+
onRetry: () => void;
|
|
53
|
+
onReplace: () => void;
|
|
54
54
|
key: string;
|
|
55
55
|
showPicInfo?: boolean;
|
|
56
56
|
renderPicInfo?: (renderFileItemProps: RenderFileItemProps) => ReactNode;
|
|
57
|
-
|
|
58
|
-
|
|
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;
|