diff --git a/components/anchor/AnchorLink.tsx b/components/anchor/AnchorLink.tsx index a1c553bc6a12..dab6ec2015af 100644 --- a/components/anchor/AnchorLink.tsx +++ b/components/anchor/AnchorLink.tsx @@ -8,6 +8,7 @@ import { ConfigConsumer, ConfigConsumerProps } from '../config-provider'; export interface AnchorLinkProps { prefixCls?: string; href: string; + target: string; title: React.ReactNode; children?: React.ReactNode; className?: string; @@ -52,7 +53,7 @@ class AnchorLink extends React.Component { }; renderAnchorLink = ({ getPrefixCls }: ConfigConsumerProps) => { - const { prefixCls: customizePrefixCls, href, title, children, className } = this.props; + const { prefixCls: customizePrefixCls, href, title, children, className, target } = this.props; const prefixCls = getPrefixCls('anchor', customizePrefixCls); const active = this.context.antAnchor.activeLink === href; const wrapperClassName = classNames(className, `${prefixCls}-link`, { @@ -67,6 +68,7 @@ class AnchorLink extends React.Component { className={titleClassName} href={href} title={typeof title === 'string' ? title : ''} + target={target} onClick={this.handleClick} > {title} diff --git a/components/anchor/__tests__/__snapshots__/demo.test.js.snap b/components/anchor/__tests__/__snapshots__/demo.test.js.snap index 4f5e30d5aedb..d1cce5bd52e1 100644 --- a/components/anchor/__tests__/__snapshots__/demo.test.js.snap +++ b/components/anchor/__tests__/__snapshots__/demo.test.js.snap @@ -41,6 +41,18 @@ exports[`renders ./components/anchor/demo/basic.md correctly 1`] = ` Static demo + @@ -17409,30 +17440,61 @@ exports[`ConfigProvider components Upload normal 1`] = ` title="xxx.png" > xxx.png + + + + + + + + + + + + - - - @@ -17479,30 +17541,61 @@ exports[`ConfigProvider components Upload prefixCls 1`] = ` title="xxx.png" > xxx.png + + + + + + + + + + + + - - - diff --git a/components/input-number/index.en-US.md b/components/input-number/index.en-US.md index 7b956e683b9f..290768899631 100644 --- a/components/input-number/index.en-US.md +++ b/components/input-number/index.en-US.md @@ -27,6 +27,7 @@ When a numeric value needs to be provided. | step | The number to which the current value is increased or decreased. It can be an integer or decimal. | number\|string | 1 | | | value | current value | number | | | | onChange | The callback triggered when the value is changed. | function(value: number \| string) | | | +| onPressEnter | The callback function that is triggered when Enter key is pressed. | function(e) | | | ## Methods diff --git a/components/input-number/index.tsx b/components/input-number/index.tsx index 358aaf569f7a..2cfc45099912 100644 --- a/components/input-number/index.tsx +++ b/components/input-number/index.tsx @@ -29,6 +29,7 @@ export interface InputNumberProps name?: string; id?: string; precision?: number; + onPressEnter?: React.KeyboardEventHandler; } export default class InputNumber extends React.Component { diff --git a/components/input-number/index.zh-CN.md b/components/input-number/index.zh-CN.md index 238971aca957..babb3d3f29d0 100644 --- a/components/input-number/index.zh-CN.md +++ b/components/input-number/index.zh-CN.md @@ -30,6 +30,7 @@ title: InputNumber | step | 每次改变步数,可以为小数 | number\|string | 1 | | | value | 当前值 | number | | | | onChange | 变化回调 | Function(value: number \| string) | | | +| onPressEnter | 按下回车的回调 | function(e) | | | ## 方法 diff --git a/components/locale/default.tsx b/components/locale/default.tsx index eabf51c069f4..012f9b4643cc 100644 --- a/components/locale/default.tsx +++ b/components/locale/default.tsx @@ -42,6 +42,7 @@ export default { removeFile: 'Remove file', uploadError: 'Upload error', previewFile: 'Preview file', + downloadFile: 'Download file', }, Empty: { description: 'No Data', diff --git a/components/locale/zh_CN.tsx b/components/locale/zh_CN.tsx index 6dd924069e3f..67534a324a1f 100644 --- a/components/locale/zh_CN.tsx +++ b/components/locale/zh_CN.tsx @@ -42,6 +42,7 @@ export default { removeFile: '删除文件', uploadError: '上传错误', previewFile: '预览文件', + downloadFile: '下载文件', }, Empty: { description: '暂无数据', diff --git a/components/page-header/__tests__/__snapshots__/demo.test.js.snap b/components/page-header/__tests__/__snapshots__/demo.test.js.snap index d61840b8916f..680e2981ba72 100644 --- a/components/page-header/__tests__/__snapshots__/demo.test.js.snap +++ b/components/page-header/__tests__/__snapshots__/demo.test.js.snap @@ -1,410 +1,361 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`renders ./components/page-header/demo/actions.md correctly 1`] = ` - + + + - - image.png - + + image.png + + + + + - - image.png - + + image.png + + + + + - - xxx.png - + + xxx.png + + + + + + + + + + + + + + - - - - - - @@ -888,38 +1188,72 @@ exports[`renders ./components/upload/demo/picture-style.md correctly 1`] = ` src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png" /> - - xxx.png - + + xxx.png + + + + + + + + + + + + + + - - - - - - diff --git a/components/upload/__tests__/__snapshots__/uploadlist.test.js.snap b/components/upload/__tests__/__snapshots__/uploadlist.test.js.snap index 20a9c1020284..d609ca8a535c 100644 --- a/components/upload/__tests__/__snapshots__/uploadlist.test.js.snap +++ b/components/upload/__tests__/__snapshots__/uploadlist.test.js.snap @@ -58,30 +58,37 @@ exports[`Upload List handle error 1`] = ` title="foo.png" > foo.png + + + + + + + - - -
@@ -167,30 +174,37 @@ exports[`Upload List should be uploading when upload a file 1`] = ` title="foo.png" > foo.png + + + + + + +
- - -
@@ -276,30 +290,61 @@ exports[`Upload List should be uploading when upload a file 2`] = ` title="foo.png" > foo.png + + + + + + + + + + + +
- - -
@@ -392,38 +437,72 @@ exports[`Upload List should non-image format file preview 1`] = ` - - not-image - + + not-image + + + + + + + + + + + + + +
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/components/upload/__tests__/upload.test.js b/components/upload/__tests__/upload.test.js index 483df225950b..3c59cf38328a 100644 --- a/components/upload/__tests__/upload.test.js +++ b/components/upload/__tests__/upload.test.js @@ -376,7 +376,7 @@ describe('Upload', () => { }, ]; const wrapper = mount(); - const linkNode = wrapper.find('a.ant-upload-list-item-name'); + const linkNode = wrapper.find('span.ant-upload-list-item-name > a'); expect(linkNode.props().download).toBe('image'); expect(linkNode.props().rel).toBe('noopener'); }); @@ -396,7 +396,7 @@ describe('Upload', () => { }, ]; const wrapper = mount(); - const linkNode = wrapper.find('a.ant-upload-list-item-name'); + const linkNode = wrapper.find('span.ant-upload-list-item-name > a'); expect(linkNode.props().download).toBe('image'); expect(linkNode.props().rel).toBe('noopener'); }); @@ -417,7 +417,7 @@ describe('Upload', () => { const wrapper = mount(); - wrapper.find('div.ant-upload-list-item i.anticon-close').simulate('click'); + wrapper.find('div.ant-upload-list-item i.anticon-delete').simulate('click'); setImmediate(() => { wrapper.update(); @@ -429,6 +429,33 @@ describe('Upload', () => { }); }); + it('should not stop download when return use onDownload', done => { + const mockRemove = jest.fn(() => false); + const props = { + onRemove: mockRemove, + fileList: [ + { + uid: '-1', + name: 'foo.png', + status: 'done', + url: 'http://www.baidu.com/xxx.png', + }, + ], + }; + + const wrapper = mount( {}} />); + + wrapper.find('div.ant-upload-list-item i.anticon-download').simulate('click'); + + setImmediate(() => { + wrapper.update(); + + expect(props.fileList).toHaveLength(1); + expect(props.fileList[0].status).toBe('done'); + done(); + }); + }); + // https://github.com/ant-design/ant-design/issues/14439 it('should allow call abort function through upload instance', () => { const wrapper = mount( diff --git a/components/upload/__tests__/uploadlist.test.js b/components/upload/__tests__/uploadlist.test.js index 0f0b73c968e1..e81e583255a4 100644 --- a/components/upload/__tests__/uploadlist.test.js +++ b/components/upload/__tests__/uploadlist.test.js @@ -121,7 +121,7 @@ describe('Upload List', () => { wrapper .find('.ant-upload-list-item') .at(0) - .find('.anticon-close') + .find('.anticon-delete') .simulate('click'); await sleep(400); wrapper.update(); @@ -202,6 +202,36 @@ describe('Upload List', () => { expect(handleChange.mock.calls[0][0].fileList).toHaveLength(3); }); + it('In the case of listType=picture, the error status does not show the download.', () => { + const file = { status: 'error', uid: 'file' }; + const wrapper = mount( + + + , + ); + expect(wrapper.find('div.ant-upload-list-item i.anticon-download').length).toBe(0); + }); + + it('In the case of listType=picture-card, the error status does not show the download.', () => { + const file = { status: 'error', uid: 'file' }; + const wrapper = mount( + + + , + ); + expect(wrapper.find('div.ant-upload-list-item i.anticon-download').length).toBe(0); + }); + + it('In the case of listType=text, the error status does not show the download.', () => { + const file = { status: 'error', uid: 'file' }; + const wrapper = mount( + + + , + ); + expect(wrapper.find('div.ant-upload-list-item i.anticon-download').length).toBe(0); + }); + it('should support onPreview', () => { const handlePreview = jest.fn(); const wrapper = mount( @@ -248,6 +278,52 @@ describe('Upload List', () => { expect(handleChange.mock.calls.length).toBe(2); }); + it('should support onDownload', async () => { + const handleDownload = jest.fn(); + const wrapper = mount( + + + , + ); + wrapper + .find('.anticon-download') + .at(0) + .simulate('click'); + }); + + it('should support no onDownload', async () => { + const wrapper = mount( + + + , + ); + wrapper + .find('.anticon-download') + .at(0) + .simulate('click'); + }); + describe('should generate thumbUrl from file', () => { [ { width: 100, height: 200, name: 'height large than width' }, @@ -431,6 +507,13 @@ describe('Upload List', () => { expect(wrapper.handlePreview()).toBe(undefined); }); + it('return when prop onDownload not exists', () => { + const file = new File([''], 'test.txt', { type: 'text/plain' }); + const items = [{ uid: 'upload-list-item', url: '' }]; + const wrapper = mount().instance(); + expect(wrapper.handleDownload(file)).toBe(undefined); + }); + it('previewFile should work correctly', async () => { const file = new File([''], 'test.txt', { type: 'text/plain' }); const items = [{ uid: 'upload-list-item', url: '' }]; @@ -440,6 +523,28 @@ describe('Upload List', () => { return wrapper.props.previewFile(file); }); + it('downloadFile should work correctly', async () => { + const file = new File([''], 'test.txt', { type: 'text/plain' }); + const items = [{ uid: 'upload-list-item', url: '' }]; + const wrapper = mount( + {}} + locale={{ downloadFile: '' }} + />, + ).instance(); + return wrapper.props.onDownload(file); + }); + + it('downloadFile is true should work correctly', async () => { + const items = [{ uid: 'upload-list-item', url: '' }]; + const wrapper = mount( + , + ).instance(); + return expect(wrapper.props.onDownload).toBe(true); + }); + it('extname should work correctly when url not exists', () => { const items = [{ uid: 'upload-list-item', url: '' }]; const wrapper = mount( @@ -448,6 +553,21 @@ describe('Upload List', () => { expect(wrapper.find('.ant-upload-list-item-thumbnail').length).toBe(2); }); + it('extname should work correctly when url exists', () => { + const items = [{ status: 'done', uid: 'upload-list-item', url: '/example' }]; + const wrapper = mount( + { + expect(file.url).toBe('/example'); + }} + items={items} + locale={{ downloadFile: '' }} + />, + ); + wrapper.find('div.ant-upload-list-item i.anticon-download').simulate('click'); + }); + it('when picture-card is loading, icon should render correctly', () => { const items = [{ status: 'uploading', uid: 'upload-list-item' }]; const wrapper = mount( diff --git a/components/upload/index.en-US.md b/components/upload/index.en-US.md index c0b1e33e1223..1c8dbc066a74 100644 --- a/components/upload/index.en-US.md +++ b/components/upload/index.en-US.md @@ -32,13 +32,14 @@ Uploading is the process of publishing information (web pages, text, pictures, v | 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 | | | name | The name of uploading file | string | 'file' | | | previewFile | Customize preview file logic | (file: File \| Blob) => Promise | - | 3.17.0 | -| showUploadList | Whether to show default upload list, could be an object to specify `showPreviewIcon` and `showRemoveIcon` individually | Boolean or { showPreviewIcon?: boolean, showRemoveIcon?: boolean } | true | | +| showUploadList | Whether to show default upload list, could be an object to specify `showPreviewIcon` , `showRemoveIcon` and `showDownloadIcon` individually | Boolean or { showPreviewIcon?: boolean, showRemoveIcon?: boolean } | true | | | supportServerRender | Need to be turned on while the server side is rendering | boolean | false | | | withCredentials | ajax upload with cookie sent | boolean | false | | | openFileDialogOnClick | click open file dialog | boolean | true | 3.10.0 | | onChange | A callback function, can be executed when uploading state is changing, see [onChange](#onChange) | Function | - | | | onPreview | A callback function, will be executed when file link or preview icon is clicked | Function(file) | - | | | onRemove | A callback function, will be executed when removing file button is clicked, remove event will be prevented when return value is `false` or a Promise which resolve(false) or reject | Function(file): `boolean | Promise` | - | | +| onDownload   | Click the method to download the file, pass the method to perform the method logic, do not pass the default jump to the new TAB.               | Function(file): void | Jump to new TAB   | | | transformFile   | Customize transform file before request | Function(file): `string | Blob | File | Promise` | - | 3.21.0 | ### onChange diff --git a/components/upload/index.zh-CN.md b/components/upload/index.zh-CN.md index 3a1d5f5b528e..73fd5c5eb6bc 100644 --- a/components/upload/index.zh-CN.md +++ b/components/upload/index.zh-CN.md @@ -33,13 +33,14 @@ title: Upload | multiple | 是否支持多选文件,`ie10+` 支持。开启后按住 ctrl 可选择多个文件 | boolean | false | | | name | 发到后台的文件参数名 | string | 'file' | | | previewFile | 自定义文件预览逻辑 | (file: File \| Blob) => Promise | 无 | 3.17.0 | -| showUploadList | 是否展示文件列表, 可设为一个对象,用于单独设定 `showPreviewIcon` 和 `showRemoveIcon` | Boolean or { showPreviewIcon?: boolean, showRemoveIcon?: boolean } | true | | +| showUploadList | 是否展示文件列表, 可设为一个对象,用于单独设定 `showPreviewIcon` , `showRemoveIcon` 和 `showDownloadIcon` | Boolean or { showPreviewIcon?: boolean, showRemoveIcon?: boolean, showDownloadIcon?: boolean } | true | | | supportServerRender | 服务端渲染时需要打开这个 | boolean | false | | | withCredentials | 上传请求时是否携带 cookie | boolean | false | | | openFileDialogOnClick | 点击打开文件对话框 | boolean | true | 3.10.0 | | onChange | 上传文件改变时的状态,详见 [onChange](#onChange) | Function | 无 | | | onPreview | 点击文件链接或预览图标时的回调 | Function(file) | 无 | | | onRemove   | 点击移除文件时的回调,返回值为 false 时不移除。支持返回一个 Promise 对象,Promise 对象 resolve(false) 或 reject 时不移除。               | Function(file): `boolean | Promise` | 无   | | +| onDownload   | 点击下载文件时的方法,传方法择执行方法逻辑,不传则默认跳转新标签页。               | Function(file): void | 跳转新标签页   | | | transformFile   | 在上传之前转换文件。支持返回一个 Promise 对象   | Function(file): `string | Blob | File | Promise` | 无   | 3.21.0 | ### onChange diff --git a/components/upload/interface.tsx b/components/upload/interface.tsx index f24b2ff8435d..b5326ead3c4d 100755 --- a/components/upload/interface.tsx +++ b/components/upload/interface.tsx @@ -40,11 +40,13 @@ export interface UploadChangeParam { export interface ShowUploadListInterface { showRemoveIcon?: boolean; showPreviewIcon?: boolean; + showDownloadIcon?: boolean; } export interface UploadLocale { uploading?: string; removeFile?: string; + downloadFile?: string; uploadError?: string; previewFile?: string; } @@ -53,7 +55,9 @@ export type UploadType = 'drag' | 'select'; export type UploadListType = 'text' | 'picture' | 'picture-card'; type PreviewFileHandler = (file: File | Blob) => PromiseLike; -type TransformFileHandler = (file: UploadFile) => string | Blob | File | PromiseLike; +type TransformFileHandler = ( + file: UploadFile, +) => string | Blob | File | PromiseLike; export interface UploadProps { type?: UploadType; @@ -72,6 +76,7 @@ export interface UploadProps { listType?: UploadListType; className?: string; onPreview?: (file: UploadFile) => void; + onDownload?: (file: UploadFile) => void; onRemove?: (file: UploadFile) => void | boolean | Promise; supportServerRender?: boolean; style?: React.CSSProperties; @@ -94,11 +99,13 @@ export interface UploadState { export interface UploadListProps { listType?: UploadListType; onPreview?: (file: UploadFile) => void; + onDownload?: (file: UploadFile) => void; onRemove?: (file: UploadFile) => void | boolean; items?: Array; progressAttr?: Object; prefixCls?: string; showRemoveIcon?: boolean; + showDownloadIcon?: boolean; showPreviewIcon?: boolean; locale: UploadLocale; previewFile?: PreviewFileHandler; diff --git a/components/upload/style/index.less b/components/upload/style/index.less index 328278335903..fc027122a0b4 100644 --- a/components/upload/style/index.less +++ b/components/upload/style/index.less @@ -147,13 +147,27 @@ font-size: @font-size-base; &-name { display: inline-block; - width: 100%; padding-left: @font-size-base + 8px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } + &-card-actions { + position: absolute; + right: 0; + opacity: 0; + &.picture { + top: 25px; + line-height: 1; + opacity: 1; + } + .anticon { + padding-right: 5px; + color: rgba(0, 0, 0, 0.45); + } + } + &-info { height: 100%; padding: 0 12px 0 4px; @@ -196,14 +210,21 @@ opacity: 1; } + &:hover &-card-actions { + opacity: 1; + } + &-error, &-error .@{iconfont-css-prefix}-paper-clip, &-error &-name { color: @error-color; } - &-error .@{iconfont-css-prefix}-close { - color: @error-color !important; + &-error &-card-actions { + .anticon { + padding-right: 5px; + color: @error-color; + } opacity: 1; } @@ -281,7 +302,7 @@ box-sizing: border-box; max-width: 100%; margin: 0 0 0 8px; - padding-right: 8px; + padding-right: 48px; padding-left: 48px; overflow: hidden; line-height: 44px; @@ -353,6 +374,7 @@ transition: all 0.3s; .@{iconfont-css-prefix}-eye-o, + .@{iconfont-css-prefix}-download, .@{iconfont-css-prefix}-delete { z-index: 10; width: 16px; diff --git a/package.json b/package.json index 701910655f65..e04bf3713d60 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "rc-dropdown": "~2.4.1", "rc-editor-mention": "^1.1.13", "rc-form": "^2.4.5", - "rc-input-number": "~4.4.5", + "rc-input-number": "~4.5.0", "rc-mentions": "~0.4.0", "rc-menu": "~7.4.23", "rc-notification": "~3.3.1",