Skip to content

Commit cd4eff6

Browse files
committed
refactor(ui): add DUploadList to display upload list
BREAKING CHANGE: update api of upload.
1 parent 8db6af8 commit cd4eff6

18 files changed

Lines changed: 316 additions & 146 deletions

File tree

packages/ui/src/components/form/demos/12.SupportComponents.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,8 +203,22 @@ export default function Demo() {
203203
</DForm.Item>
204204
<DForm.Item dFormControls={{ Upload: 'Upload!' }} dLabel="Upload">
205205
{({ Upload }) => (
206-
<DUpload dFormControl={Upload} dXHRRequest={{ url: 'https://rd-upload.free.beeceptor.com' }}>
207-
<DButton dIcon={<UploadOutlined />}>Upload</DButton>
206+
<DUpload
207+
dFormControl={Upload}
208+
dTrigger={
209+
<DButton className="mb-2" dIcon={<UploadOutlined />}>
210+
Upload
211+
</DButton>
212+
}
213+
dList={<DUpload.List />}
214+
dXHRRequest={{ url: 'https://rd-upload.free.beeceptor.com' }}
215+
>
216+
{({ trigger, list }) => (
217+
<>
218+
{trigger}
219+
{list}
220+
</>
221+
)}
208222
</DUpload>
209223
)}
210224
</DForm.Item>

packages/ui/src/components/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ export { DTree } from './tree';
154154
export type { DTreeSelectProps } from './tree-select';
155155
export { DTreeSelect } from './tree-select';
156156

157-
export type { DUploadProps, DUploadActionProps, DUploadPictureButtonProps } from './upload';
157+
export type { DUploadProps, DUploadActionProps, DUploadListProps, DUploadPictureButtonProps } from './upload';
158158
export { DUpload } from './upload';
159159

160160
export type { DVirtualScrollProps } from './virtual-scroll';

packages/ui/src/components/root/contex.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ import type { DTooltipProps } from '../tooltip';
5757
import type { DTransferProps } from '../transfer';
5858
import type { DTreeProps } from '../tree';
5959
import type { DTreeSelectProps } from '../tree-select';
60-
import type { DUploadProps, DUploadActionProps, DUploadPictureButtonProps } from '../upload';
60+
import type { DUploadProps, DUploadActionProps, DUploadListProps, DUploadPictureButtonProps } from '../upload';
6161
import type { DVirtualScrollProps } from '../virtual-scroll';
6262
import type resources from './resources.json';
6363
import type { DRefExtra } from '@react-devui/hooks/useRefExtra';
@@ -142,6 +142,7 @@ export type DComponentConfig = {
142142
DTreeSelect: DTreeSelectProps<any, any>;
143143
DUpload: DUploadProps;
144144
'DUpload.Action': DUploadActionProps;
145+
'DUpload.List': DUploadListProps;
145146
'DUpload.PictureButton': DUploadPictureButtonProps;
146147
DVirtualScroll: DVirtualScrollProps<any>;
147148
} & { DIcon: Omit<DIconProps, 'dIcon'> };

packages/ui/src/components/upload/List.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { DCollapseTransition } from '../_transition';
1414
import { DProgress } from '../progress';
1515
import { usePrefixConfig } from '../root';
1616

17-
export interface DListProps {
17+
export interface DListProps extends Omit<React.HTMLAttributes<HTMLUListElement>, 'children'> {
1818
dFileList: DUploadFile[];
1919
dDefaultActions?: {
2020
preview?: (file: DUploadFile) => void;
@@ -77,9 +77,7 @@ export function DList(props: DListProps): JSX.Element | null {
7777
{(collapseRef, collapseStyle) => (
7878
<li
7979
ref={collapseRef}
80-
className={getClassName(`${dPrefix}upload__list-item`, `${dPrefix}upload__list-item--${file.status}`, {
81-
[`${dPrefix}upload__list-item--first`]: index === 0,
82-
})}
80+
className={getClassName(`${dPrefix}upload__list-item`, `${dPrefix}upload__list-item--${file.status}`)}
8381
style={collapseStyle}
8482
>
8583
<div className={`${dPrefix}upload__list-icon`}>

packages/ui/src/components/upload/Picture.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ import { DTransition } from '../_transition';
1414
import { DProgress } from '../progress';
1515
import { usePrefixConfig, useTranslation } from '../root';
1616

17-
export interface DPictureProps {
18-
children: React.ReactNode;
17+
export interface DPictureProps extends Omit<React.HTMLAttributes<HTMLUListElement>, 'children'> {
18+
children?: React.ReactNode;
1919
dFileList: DUploadFile[];
2020
dDefaultActions?: {
2121
preview?: (file: DUploadFile) => void;

packages/ui/src/components/upload/PictureList.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { DCollapseTransition } from '../_transition';
1414
import { DProgress } from '../progress';
1515
import { usePrefixConfig } from '../root';
1616

17-
export interface DPictureListProps {
17+
export interface DPictureListProps extends Omit<React.HTMLAttributes<HTMLUListElement>, 'children'> {
1818
dFileList: DUploadFile[];
1919
dDefaultActions?: {
2020
preview?: (file: DUploadFile) => void;
@@ -80,10 +80,7 @@ export function DPictureList(props: DPictureListProps): JSX.Element | null {
8080
className={getClassName(
8181
`${dPrefix}upload__list-item`,
8282
`${dPrefix}upload__list-item--picture`,
83-
`${dPrefix}upload__list-item--${file.status}`,
84-
{
85-
[`${dPrefix}upload__list-item--first`]: index === 0,
86-
}
83+
`${dPrefix}upload__list-item--${file.status}`
8784
)}
8885
style={collapseStyle}
8986
>

packages/ui/src/components/upload/Upload.tsx

Lines changed: 67 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import type { DId } from '../../utils/types';
22
import type { DFormControl } from '../form';
3+
import type { DUploadListPrivateProps } from './UploadList';
34

4-
import { isNull, isNumber } from 'lodash';
5+
import { isNumber } from 'lodash';
56
import React, { useRef } from 'react';
67

78
import { useForkRef, useUnmount } from '@react-devui/hooks';
@@ -11,10 +12,8 @@ import { useDValue } from '../../hooks';
1112
import { registerComponentMate } from '../../utils';
1213
import { useFormControl } from '../form';
1314
import { useComponentConfig, usePrefixConfig } from '../root';
14-
import { DList } from './List';
15-
import { DPicture } from './Picture';
16-
import { DPictureList } from './PictureList';
1715
import { DUploadAction } from './UploadAction';
16+
import { DUploadList } from './UploadList';
1817
import { DUploadPictureButton } from './UploadPictureButton';
1918

2019
export type DUploadFileStatus = 'load' | 'error' | 'progress' | null;
@@ -31,26 +30,22 @@ export interface DUploadFile {
3130
[index: string | symbol]: any;
3231
}
3332

34-
export interface DUploadProps extends React.InputHTMLAttributes<HTMLInputElement> {
35-
children: React.ReactElement | null;
33+
export interface DUploadProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'children'> {
34+
children?: (nodes: { trigger: React.ReactNode; list: React.ReactNode }) => React.ReactNode;
3635
dFormControl?: DFormControl;
3736
dModel?: DUploadFile[];
38-
dXHRRequest: {
37+
dTrigger?: React.ReactElement;
38+
dList?: React.ReactElement;
39+
dXHRRequest?: {
3940
method?: string;
4041
url: string | URL;
4142
responseType?: XMLHttpRequestResponseType;
4243
header?: { [index: string]: string };
4344
body?: (file: string | Blob) => Document | XMLHttpRequestBodyInit | null | undefined;
4445
custom?: (xhr: XMLHttpRequest) => void;
4546
};
46-
dListType?: 'list' | 'picture' | 'picture-list' | false;
4747
dDrag?: boolean;
4848
dMax?: number;
49-
dDefaultActions?: {
50-
preview?: (file: DUploadFile) => void;
51-
download?: (file: DUploadFile) => void;
52-
};
53-
dActions?: (file: DUploadFile, index: number) => React.ReactNode[];
5449
dCustomUpload?: (files: FileList) => void;
5550
dBeforeUpload?: (file: File, files: FileList) => boolean | string | Blob | Promise<boolean | string | Blob>;
5651
onModelChange?: (
@@ -69,12 +64,11 @@ function Upload(props: DUploadProps, ref: React.ForwardedRef<HTMLInputElement>):
6964
children,
7065
dFormControl,
7166
dModel,
67+
dTrigger,
68+
dList,
7269
dXHRRequest,
73-
dListType = 'list',
7470
dDrag = false,
7571
dMax,
76-
dDefaultActions,
77-
dActions,
7872
dCustomUpload,
7973
dBeforeUpload,
8074
onModelChange,
@@ -197,7 +191,7 @@ function Upload(props: DUploadProps, ref: React.ForwardedRef<HTMLInputElement>):
197191
return formData;
198192
},
199193
custom,
200-
} = dXHRRequest;
194+
} = dXHRRequest!;
201195

202196
xhr.open(method, url);
203197
xhr.responseType = responseType;
@@ -235,65 +229,49 @@ function Upload(props: DUploadProps, ref: React.ForwardedRef<HTMLInputElement>):
235229
}
236230
};
237231

238-
const child = (() => {
239-
if (isNull(children)) {
240-
return children;
241-
}
242-
243-
let dragProps: React.HTMLAttributes<HTMLElement> = {};
244-
if (dDrag) {
245-
dragProps = {
246-
onDragEnter: (e) => {
247-
children.props.onDragEnter?.(e);
248-
249-
e.stopPropagation();
250-
e.preventDefault();
251-
},
252-
onDragOver: (e) => {
253-
children.props.onDragOver?.(e);
254-
255-
e.stopPropagation();
256-
e.preventDefault();
257-
},
258-
onDrop: (e) => {
259-
children.props.onDrop?.(e);
232+
const trigger = (() => {
233+
if (dTrigger) {
234+
let dragProps: React.HTMLAttributes<HTMLElement> = {};
235+
if (dDrag) {
236+
dragProps = {
237+
onDragEnter: (e) => {
238+
dTrigger.props.onDragEnter?.(e);
239+
240+
e.stopPropagation();
241+
e.preventDefault();
242+
},
243+
onDragOver: (e) => {
244+
dTrigger.props.onDragOver?.(e);
245+
246+
e.stopPropagation();
247+
e.preventDefault();
248+
},
249+
onDrop: (e) => {
250+
dTrigger.props.onDrop?.(e);
251+
252+
e.stopPropagation();
253+
e.preventDefault();
254+
255+
const dt = e.dataTransfer;
256+
const files = dt.files;
257+
handleFiles(files);
258+
},
259+
};
260+
}
260261

261-
e.stopPropagation();
262-
e.preventDefault();
262+
return React.cloneElement<React.HTMLAttributes<HTMLElement>>(dTrigger, {
263+
...dragProps,
264+
onClick: (e) => {
265+
dTrigger.props.onClick?.(e);
263266

264-
const dt = e.dataTransfer;
265-
const files = dt.files;
266-
handleFiles(files);
267+
if (inputRef.current) {
268+
inputRef.current.click();
269+
}
267270
},
268-
};
271+
});
269272
}
270-
271-
return React.cloneElement<React.HTMLAttributes<HTMLElement>>(children, {
272-
...dragProps,
273-
onClick: (e) => {
274-
children.props.onClick?.(e);
275-
276-
if (inputRef.current) {
277-
inputRef.current.click();
278-
}
279-
},
280-
});
281273
})();
282274

283-
const listProps = {
284-
dFileList: fileList,
285-
dDefaultActions,
286-
onRemove: (file: DUploadFile) => {
287-
onRemove?.(file);
288-
289-
const newList = changeFileList((draft) => {
290-
const index = draft.findIndex((f) => f.uid === file.uid);
291-
draft.splice(index, 1);
292-
});
293-
onModelChange?.(newList, { type: 'remove', files: [file] });
294-
},
295-
};
296-
297275
return (
298276
<>
299277
<input
@@ -312,32 +290,34 @@ function Upload(props: DUploadProps, ref: React.ForwardedRef<HTMLInputElement>):
312290
e.currentTarget.value = '';
313291
}}
314292
></input>
315-
{dListType === 'list' ? (
316-
<>
317-
{child}
318-
<DList {...listProps} dActions={dActions ?? (() => [<DUploadAction dPreset="remove" />])}></DList>
319-
</>
320-
) : dListType === 'picture' ? (
321-
<DPicture {...listProps} dActions={dActions ?? (() => [<DUploadAction dPreset="preview" />, <DUploadAction dPreset="remove" />])}>
322-
{child}
323-
</DPicture>
324-
) : dListType === 'picture-list' ? (
325-
<>
326-
{child}
327-
<DPictureList {...listProps} dActions={dActions ?? (() => [<DUploadAction dPreset="remove" />])}></DPictureList>
328-
</>
329-
) : (
330-
child
331-
)}
293+
{children?.({
294+
trigger,
295+
list:
296+
dList &&
297+
React.cloneElement<DUploadListPrivateProps>(dList, {
298+
__fileList: fileList,
299+
__onRemove(file) {
300+
onRemove?.(file);
301+
302+
const newList = changeFileList((draft) => {
303+
const index = draft.findIndex((f) => f.uid === file.uid);
304+
draft.splice(index, 1);
305+
});
306+
onModelChange?.(newList, { type: 'remove', files: [file] });
307+
},
308+
}),
309+
})}
332310
</>
333311
);
334312
}
335313

336314
export const DUpload: {
337315
(props: DUploadProps & React.RefAttributes<HTMLInputElement>): ReturnType<typeof Upload>;
338316
Action: typeof DUploadAction;
317+
List: typeof DUploadList;
339318
PictureButton: typeof DUploadPictureButton;
340319
} = React.forwardRef(Upload) as any;
341320

342321
DUpload.Action = DUploadAction;
322+
DUpload.List = DUploadList;
343323
DUpload.PictureButton = DUploadPictureButton;
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import type { DUploadFile } from './Upload';
2+
3+
import React from 'react';
4+
5+
import { registerComponentMate } from '../../utils';
6+
import { useComponentConfig } from '../root';
7+
import { DList } from './List';
8+
import { DPicture } from './Picture';
9+
import { DPictureList } from './PictureList';
10+
import { DUploadAction } from './UploadAction';
11+
12+
export interface DUploadListProps extends Omit<React.HTMLAttributes<HTMLUListElement>, 'children'> {
13+
dType?: 'list' | 'picture' | 'picture-list';
14+
dDefaultActions?: {
15+
preview?: (file: DUploadFile) => void;
16+
download?: (file: DUploadFile) => void;
17+
};
18+
dActions?: (file: DUploadFile, index: number) => React.ReactNode[];
19+
}
20+
21+
export interface DUploadListPrivateProps {
22+
__fileList: DUploadFile[];
23+
__onRemove: (file: DUploadFile) => void;
24+
}
25+
26+
const { COMPONENT_NAME } = registerComponentMate({ COMPONENT_NAME: 'DUpload.List' as const });
27+
export function DUploadList(props: DUploadListProps): JSX.Element | null {
28+
const {
29+
dType = 'list',
30+
dDefaultActions,
31+
dActions,
32+
__fileList,
33+
__onRemove,
34+
35+
...restProps
36+
} = useComponentConfig(COMPONENT_NAME, props as DUploadListProps & DUploadListPrivateProps);
37+
38+
return dType === 'list' ? (
39+
<DList
40+
{...restProps}
41+
dFileList={__fileList}
42+
dDefaultActions={dDefaultActions}
43+
dActions={dActions ?? (() => [<DUploadAction dPreset="remove" />])}
44+
onRemove={__onRemove}
45+
></DList>
46+
) : dType === 'picture' ? (
47+
<DPicture
48+
{...restProps}
49+
dFileList={__fileList}
50+
dDefaultActions={dDefaultActions}
51+
dActions={dActions ?? (() => [<DUploadAction dPreset="preview" />, <DUploadAction dPreset="remove" />])}
52+
onRemove={__onRemove}
53+
/>
54+
) : (
55+
<DPictureList
56+
{...restProps}
57+
dFileList={__fileList}
58+
dDefaultActions={dDefaultActions}
59+
dActions={dActions ?? (() => [<DUploadAction dPreset="remove" />])}
60+
onRemove={__onRemove}
61+
></DPictureList>
62+
);
63+
}

0 commit comments

Comments
 (0)