diff --git a/components/upload/Upload.tsx b/components/upload/Upload.tsx index e071499303..78865c1f3b 100644 --- a/components/upload/Upload.tsx +++ b/components/upload/Upload.tsx @@ -370,15 +370,18 @@ export default defineComponent({ delete rcUploadProps.id; } - const rtlCls = { + const wrapperCls = classNames(`${prefixCls.value}-wrapper`, className, hashId.value, { [`${prefixCls.value}-rtl`]: direction.value === 'rtl', - }; + [`${prefixCls.value}-picture-card-wrapper`]: listType === 'picture-card', + [`${prefixCls.value}-picture-circle-wrapper`]: listType === 'picture-circle', + }); if (type === 'drag') { const dragCls = classNames( + hashId.value, prefixCls.value, + `${prefixCls.value}-drag`, { - [`${prefixCls.value}-drag`]: true, [`${prefixCls.value}-drag-uploading`]: mergedFileList.value.some( file => file.status === 'uploading', ), @@ -391,10 +394,7 @@ export default defineComponent({ ); return wrapSSR( - +
( @@ -429,27 +426,15 @@ export default defineComponent({
); - if (listType === 'picture-card') { + if (listType === 'picture-card' || listType === 'picture-circle') { return wrapSSR( - + {renderUploadList(renderUploadButton, !!(children && children.length))} , ); } return wrapSSR( - + {renderUploadButton(children && children.length ? undefined : { display: 'none' })} {renderUploadList()} , diff --git a/components/upload/UploadList/ListItem.tsx b/components/upload/UploadList/ListItem.tsx index b7fcc2223f..6cade17758 100644 --- a/components/upload/UploadList/ListItem.tsx +++ b/components/upload/UploadList/ListItem.tsx @@ -108,7 +108,7 @@ export default defineComponent({ const iconNode = iconRender({ file }); let icon =
{iconNode}
; - if (listType === 'picture' || listType === 'picture-card') { + if (listType === 'picture' || listType === 'picture-card' || listType === 'picture-circle') { if (mergedStatus.value === 'uploading' || (!file.thumbUrl && !file.url)) { const uploadingClassName = { [`${prefixCls}-list-item-thumbnail`]: true, @@ -168,7 +168,7 @@ export default defineComponent({ title: locale.downloadFile, }) : null; - const downloadOrDelete = listType !== 'picture-card' && ( + const downloadOrDelete = listType !== 'picture-card' && listType !== 'picture-circle' && ( ) : null; - const pictureCardActions = listType === 'picture-card' && + const pictureCardActions = (listType === 'picture-card' || listType === 'picture-circle') && mergedStatus.value !== 'uploading' && ( {previewIcon} diff --git a/components/upload/UploadList/index.tsx b/components/upload/UploadList/index.tsx index 1768c479f9..9f013b936b 100644 --- a/components/upload/UploadList/index.tsx +++ b/components/upload/UploadList/index.tsx @@ -51,7 +51,11 @@ export default defineComponent({ motionAppear.value == true; }); watchEffect(() => { - if (props.listType !== 'picture' && props.listType !== 'picture-card') { + if ( + props.listType !== 'picture' && + props.listType !== 'picture-card' && + props.listType !== 'picture-circle' + ) { return; } (props.items || []).forEach((file: InternalUploadFile) => { @@ -108,7 +112,7 @@ export default defineComponent({ let icon: VueNode = isLoading ? : ; if (props.listType === 'picture') { icon = isLoading ? : fileIcon; - } else if (props.listType === 'picture-card') { + } else if (props.listType === 'picture-card' || props.listType === 'picture-circle') { icon = isLoading ? props.locale.uploading : fileIcon; } return icon; @@ -160,12 +164,16 @@ export default defineComponent({ delete motion.onAfterLeave; const motionConfig = { ...getTransitionGroupProps( - `${prefixCls.value}-${props.listType === 'picture-card' ? 'animate-inline' : 'animate'}`, + `${prefixCls.value}-${ + props.listType === 'picture-card' || props.listType === 'picture-circle' + ? 'animate-inline' + : 'animate' + }`, ), class: listClassNames.value, appear: motionAppear.value, }; - return props.listType !== 'picture-card' + return props.listType !== 'picture-card' && props.listType !== 'picture-circle' ? { ...motion, ...motionConfig, diff --git a/components/upload/__tests__/__snapshots__/demo.test.js.snap b/components/upload/__tests__/__snapshots__/demo.test.js.snap index 25b5ece0cb..0f5b5ca212 100644 --- a/components/upload/__tests__/__snapshots__/demo.test.js.snap +++ b/components/upload/__tests__/__snapshots__/demo.test.js.snap @@ -169,6 +169,37 @@ exports[`renders ./components/upload/demo/picture-card.vue correctly 1`] = ` `; +exports[`renders ./components/upload/demo/picture-circle.vue correctly 1`] = ` +
image.pngimage.png + + +
+
+
+
+
Uploading...
image.png + + + +
+
+
+
+
image.png + + + +
+ +
+
+
Upload
+
+
+ +
+`; + exports[`renders ./components/upload/demo/picture-style.vue correctly 1`] = `
diff --git a/components/upload/demo/avatar.vue b/components/upload/demo/avatar.vue index 38d6024586..a912b14a6d 100644 --- a/components/upload/demo/avatar.vue +++ b/components/upload/demo/avatar.vue @@ -37,6 +37,23 @@ Click to upload user's avatar, and validate size and format of picture with `bef
Upload
+ + avatar +
+ + +
Upload
+
+
diff --git a/components/upload/demo/defaultFileList.vue b/components/upload/demo/defaultFileList.vue index bb92107a73..6348855ff9 100644 --- a/components/upload/demo/defaultFileList.vue +++ b/components/upload/demo/defaultFileList.vue @@ -31,9 +31,10 @@ const fileList = ref([ { uid: '1', name: 'xxx.png', - status: 'done', + status: 'uploading', response: 'Server Error 500', // custom error message to show url: 'http://www.baidu.com/xxx.png', + percent: 33, }, { uid: '2', diff --git a/components/upload/demo/index.vue b/components/upload/demo/index.vue index 8796c28b12..ac5902198c 100644 --- a/components/upload/demo/index.vue +++ b/components/upload/demo/index.vue @@ -4,6 +4,7 @@ + @@ -35,6 +36,7 @@ import maxCountVue from './max-count.vue'; import uploadCustomActionIconVue from './upload-custom-action-icon.vue'; import uploadPngOnlyVue from './upload-png-only.vue'; import customRenderVue from './custom-render.vue'; +import PictureCircle from './picture-circle.vue'; import CN from '../index.zh-CN.md'; import US from '../index.en-US.md'; import { defineComponent } from 'vue'; @@ -59,6 +61,7 @@ export default defineComponent({ uploadCustomActionIconVue, uploadPngOnlyVue, customRenderVue, + PictureCircle, }, setup() { return {}; diff --git a/components/upload/demo/picture-circle.vue b/components/upload/demo/picture-circle.vue new file mode 100644 index 0000000000..97d0a64ecb --- /dev/null +++ b/components/upload/demo/picture-circle.vue @@ -0,0 +1,87 @@ + +--- +order: 3.1 +title: + zh-CN: 圆形照片墙 + en-US: Pictures with picture-circle type +--- + +## zh-CN + +图片卡的替代显示。 + +## en-US + +Alternative display for picture-card. + + + + diff --git a/components/upload/index.en-US.md b/components/upload/index.en-US.md index d1c478db29..3b985006e7 100644 --- a/components/upload/index.en-US.md +++ b/components/upload/index.en-US.md @@ -33,7 +33,7 @@ Uploading is the process of publishing information (web pages, text, pictures, v | iconRender | Custom show icon | v-slot:iconRender="{file: UploadFile, listType?: UploadListType}" | - | 3.0 | | | isImageUrl | Customize if render <img /> in thumbnail | (file: UploadFile) => boolean | - | 3.0 | | | itemRender | Custom item of uploadList | v-slot:itemRender="{originNode: VNode, file: UploadFile, fileList: object\[], actions: { download: function, preview: function, remove: function }" | - | 3.0 | | -| listType | Built-in stylesheets, support for three types: `text`, `picture` or `picture-card` | string | `text` | | | +| listType | Built-in stylesheets, support for three types: `text`, `picture`, `picture-card` or `picture-circle` | string | `text` | `picture-circle`(4.0) | | | maxCount | Limit the number of uploaded files. Will replace current one when `maxCount` is `1` | number | - | 3.0 | | | method | http method of upload request | string | `post` | 1.5.0 | | | multiple | Whether to support selected multiple file. `IE10+` supported. You can select multiple files with CTRL holding down while multiple is set to be true | boolean | false | | | diff --git a/components/upload/index.zh-CN.md b/components/upload/index.zh-CN.md index ecceb38deb..fe25d25eb7 100644 --- a/components/upload/index.zh-CN.md +++ b/components/upload/index.zh-CN.md @@ -34,7 +34,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*l1nlSryXib8AAA | iconRender | 自定义显示 icon | v-slot:iconRender="{file: UploadFile, listType?: UploadListType}" | - | 3.0 | | | isImageUrl | 自定义缩略图是否使用 <img /> 标签进行显示 | (file: UploadFile) => boolean | - | 3.0 | | | itemRender | 自定义上传列表项 | v-slot:itemRender="{originNode: VNode, file: UploadFile, fileList: object\[], actions: { download: function, preview: function, remove: function }" | - | 3.0 | | -| listType | 上传列表的内建样式,支持三种基本样式 `text`, `picture` 和 `picture-card` | string | `text` | | | +| listType | 上传列表的内建样式,支持三种基本样式 `text`, `picture`, `picture-card` 和 `picture-circle` | string | `text` | `picture-circle`(4.0) | | | maxCount | 限制上传数量。当为 1 时,始终用最新上传的文件代替当前文件 | number | - | 3.0 | | | method | 上传请求的 http method | string | `post` | 1.5.0 | | | multiple | 是否支持多选文件,`ie10+` 支持。开启后按住 ctrl 可选择多个文件。 | boolean | false | | | diff --git a/components/upload/interface.tsx b/components/upload/interface.tsx index f89857fad6..4ff767639b 100755 --- a/components/upload/interface.tsx +++ b/components/upload/interface.tsx @@ -71,7 +71,7 @@ export interface UploadLocale { } export type UploadType = 'drag' | 'select'; -export type UploadListType = 'text' | 'picture' | 'picture-card'; +export type UploadListType = 'text' | 'picture' | 'picture-card' | 'picture-circle'; export type UploadListProgressProps = Omit & { class?: string; style?: CSSProperties; diff --git a/components/upload/style/index.ts b/components/upload/style/index.ts index e2f2789399..e9543f192b 100644 --- a/components/upload/style/index.ts +++ b/components/upload/style/index.ts @@ -43,24 +43,30 @@ const genBaseStyle: GenerateStyle = token => { }; // ============================== Export ============================== -export default genComponentStyleHook('Upload', token => { - const { fontSizeHeading3, fontSize, lineHeight, lineWidth, controlHeightLG } = token; - const listItemHeightSM = Math.round(fontSize * lineHeight); +export default genComponentStyleHook( + 'Upload', + token => { + const { fontSizeHeading3, fontSize, lineHeight, lineWidth, controlHeightLG } = token; + const listItemHeightSM = Math.round(fontSize * lineHeight); - const uploadToken = mergeToken(token, { - uploadThumbnailSize: fontSizeHeading3 * 2, - uploadProgressOffset: listItemHeightSM / 2 + lineWidth, - uploadPicCardSize: controlHeightLG * 2.55, - }); + const uploadToken = mergeToken(token, { + uploadThumbnailSize: fontSizeHeading3 * 2, + uploadProgressOffset: listItemHeightSM / 2 + lineWidth, + uploadPicCardSize: controlHeightLG * 2.55, + }); - return [ - genBaseStyle(uploadToken), - genDraggerStyle(uploadToken), - genPictureStyle(uploadToken), - genPictureCardStyle(uploadToken), - genListStyle(uploadToken), - genMotionStyle(uploadToken), - genRtlStyle(uploadToken), - genCollapseMotion(uploadToken), - ]; -}); + return [ + genBaseStyle(uploadToken), + genDraggerStyle(uploadToken), + genPictureStyle(uploadToken), + genPictureCardStyle(uploadToken), + genListStyle(uploadToken), + genMotionStyle(uploadToken), + genRtlStyle(uploadToken), + genCollapseMotion(uploadToken), + ]; + }, + token => ({ + actionsColor: token.colorTextDescription, + }), +); diff --git a/components/upload/style/motion.ts b/components/upload/style/motion.ts index 105801927c..93ae515d5d 100644 --- a/components/upload/style/motion.ts +++ b/components/upload/style/motion.ts @@ -1,6 +1,7 @@ import { Keyframes } from '../../_util/cssinjs'; import type { UploadToken } from '.'; import type { GenerateStyle } from '../../theme/internal'; +import { initFadeMotion } from '../../style/motion'; const uploadAnimateInlineIn = new Keyframes('uploadAnimateInlineIn', { from: { @@ -44,6 +45,9 @@ const genMotionStyle: GenerateStyle = token => { }, }, }, + { + [`${componentCls}-wrapper`]: initFadeMotion(token), + }, uploadAnimateInlineIn, uploadAnimateInlineOut, ]; diff --git a/components/upload/style/picture.ts b/components/upload/style/picture.ts index ed08e79114..9fabcb7c41 100644 --- a/components/upload/style/picture.ts +++ b/components/upload/style/picture.ts @@ -1,3 +1,4 @@ +import { blue } from '@ant-design/colors'; import { TinyColor } from '@ctrl/tinycolor'; import type { UploadToken } from '.'; import type { GenerateStyle } from '../../theme/internal'; @@ -11,7 +12,11 @@ const genPictureStyle: GenerateStyle = token => { return { [`${componentCls}-wrapper`]: { // ${listCls} 增加优先级 - [`${listCls}${listCls}-picture, ${listCls}${listCls}-picture-card`]: { + [` + ${listCls}${listCls}-picture, + ${listCls}${listCls}-picture-card, + ${listCls}${listCls}-picture-circle + `]: { [itemCls]: { position: 'relative', height: uploadThumbnailSize + token.lineWidth * 2 + token.paddingXS * 2, @@ -57,10 +62,10 @@ const genPictureStyle: GenerateStyle = token => { // Adjust the color of the error icon : https://github.com/ant-design/ant-design/pull/24160 [`${itemCls}-thumbnail ${iconCls}`]: { - [`svg path[fill='#e6f7ff']`]: { + [`svg path[fill=${blue[0]}]`]: { fill: token.colorErrorBg, }, - [`svg path[fill='#1890ff']`]: { + [`svg path[fill=${blue.primary}]`]: { fill: token.colorError, }, }, @@ -74,6 +79,12 @@ const genPictureStyle: GenerateStyle = token => { }, }, }, + + [`${listCls}${listCls}-picture-circle ${itemCls}`]: { + [`&, &::before, ${itemCls}-thumbnail`]: { + borderRadius: '50%', + }, + }, }, }; }; @@ -87,7 +98,10 @@ const genPictureCardStyle: GenerateStyle = token => { const uploadPictureCardSize = token.uploadPicCardSize; return { - [`${componentCls}-wrapper${componentCls}-picture-card-wrapper`]: { + [` + ${componentCls}-wrapper${componentCls}-picture-card-wrapper, + ${componentCls}-wrapper${componentCls}-picture-circle-wrapper + `]: { ...clearFix(), display: 'inline-block', width: '100%', @@ -119,7 +133,7 @@ const genPictureCardStyle: GenerateStyle = token => { }, // list - [`${listCls}${listCls}-picture-card`]: { + [`${listCls}${listCls}-picture-card, ${listCls}${listCls}-picture-circle`]: { [`${listCls}-item-container`]: { display: 'inline-block', width: uploadPictureCardSize, @@ -221,6 +235,11 @@ const genPictureCardStyle: GenerateStyle = token => { }, }, }, + [`${componentCls}-wrapper${componentCls}-picture-circle-wrapper`]: { + [`${componentCls}${componentCls}-select`]: { + borderRadius: '50%', + }, + }, }; };