refcator:upload (#6261)

* refcator:upload

* docs:update & refactor: upload type

* Update style.ts

---------

Co-authored-by: tangjinzhou <415800467@qq.com>
pull/6265/head^2
果冻橙 2023-02-15 10:19:49 +08:00 committed by GitHub
parent 7e29eb2163
commit 0464c84afc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 727 additions and 864 deletions

View File

@ -43,7 +43,7 @@ import './timeline/style';
import './input-number/style';
// import './transfer/style';
import './tree/style';
import './upload/style';
// import './upload/style';
// import './layout/style';
// import './anchor/style';
// import './list/style';

View File

@ -44,7 +44,7 @@ import type { ComponentToken as TagComponentToken } from '../../tag/style';
import type { ComponentToken as TooltipComponentToken } from '../../tooltip/style';
import type { ComponentToken as TransferComponentToken } from '../../transfer/style';
import type { ComponentToken as TypographyComponentToken } from '../../typography/style';
// import type { ComponentToken as UploadComponentToken } from '../../upload/style';
import type { ComponentToken as UploadComponentToken } from '../../upload/style';
// import type { ComponentToken as TourComponentToken } from '../../tour/style';
// import type { ComponentToken as QRCodeComponentToken } from '../../qrcode/style';
// import type { ComponentToken as AppComponentToken } from '../../app/style';
@ -107,7 +107,7 @@ export interface ComponentTokenMap {
Menu?: MenuComponentToken;
Modal?: ModalComponentToken;
Message?: MessageComponentToken;
// Upload?: UploadComponentToken;
Upload?: UploadComponentToken;
Tooltip?: TooltipComponentToken;
// Table?: TableComponentToken;
Space?: SpaceComponentToken;

View File

@ -23,6 +23,9 @@ import type { VueNode } from '../_util/type';
import classNames from '../_util/classNames';
import { useInjectFormItemContext } from '../form';
// CSSINJS
import useStyle from './style';
export const LIST_IGNORE = `__LIST_IGNORE_${Date.now()}__`;
export default defineComponent({
@ -292,6 +295,10 @@ export default defineComponent({
});
const { prefixCls, direction } = useConfigInject('upload', props);
// style
const [wrapSSR, hashId] = useStyle(prefixCls);
const [locale] = useLocaleReceiver(
'Upload',
defaultLocale.Upload,
@ -366,6 +373,11 @@ export default defineComponent({
if (!slots.default || disabled) {
delete rcUploadProps.id;
}
const rtlCls = {
[`${prefixCls}-rtl`]: direction.value === 'rtl',
};
if (type === 'drag') {
const dragCls = classNames(
prefixCls.value,
@ -379,9 +391,14 @@ export default defineComponent({
[`${prefixCls.value}-rtl`]: direction.value === 'rtl',
},
attrs.class,
hashId.value,
);
return (
<span>
return wrapSSR(
<span
{...attrs}
class={classNames(`${prefixCls.value}-wrapper`, rtlCls, className, hashId.value)}
>
<div
class={dragCls}
onDrop={onFileDrop}
@ -399,7 +416,7 @@ export default defineComponent({
</VcUpload>
</div>
{renderUploadList()}
</span>
</span>,
);
}
@ -417,17 +434,29 @@ export default defineComponent({
);
if (listType === 'picture-card') {
return (
<span class={classNames(`${prefixCls.value}-picture-card-wrapper`, attrs.class)}>
return wrapSSR(
<span
{...attrs}
class={classNames(
`${prefixCls.value}-wrapper`,
`${prefixCls.value}-picture-card-wrapper`,
rtlCls,
attrs.class,
hashId.value,
)}
>
{renderUploadList(renderUploadButton, !!(children && children.length))}
</span>
</span>,
);
}
return (
<span class={attrs.class}>
return wrapSSR(
<span
{...attrs}
class={classNames(`${prefixCls.value}-wrapper`, rtlCls, attrs.class, hashId.value)}
>
{renderUploadButton(children && children.length ? undefined : { display: 'none' })}
{renderUploadList()}
</span>
</span>,
);
};
},

View File

@ -1,5 +1,5 @@
import { computed, defineComponent, onBeforeUnmount, onMounted, ref } from 'vue';
import type { ExtractPropTypes, PropType, CSSProperties } from 'vue';
import type { ExtractPropTypes, CSSProperties } from 'vue';
import EyeOutlined from '@ant-design/icons-vue/EyeOutlined';
import DeleteOutlined from '@ant-design/icons-vue/DeleteOutlined';
import DownloadOutlined from '@ant-design/icons-vue/DownloadOutlined';
@ -16,36 +16,39 @@ import type {
import type { VueNode } from '../../_util/type';
import useConfigInject from '../../config-provider/hooks/useConfigInject';
import Transition, { getTransitionProps } from '../../_util/transition';
import { booleanType, stringType, functionType, arrayType, objectType } from '../../_util/type';
export const listItemProps = () => {
return {
prefixCls: String,
locale: { type: Object as PropType<UploadLocale>, default: undefined as UploadLocale },
file: Object as PropType<UploadFile>,
items: Array as PropType<UploadFile[]>,
listType: String as PropType<UploadListType>,
isImgUrl: Function as PropType<(file: UploadFile) => boolean>,
locale: objectType<UploadLocale>(undefined as UploadLocale),
file: objectType<UploadFile>(),
items: arrayType<UploadFile[]>(),
listType: stringType<UploadListType>(),
isImgUrl: functionType<(file: UploadFile) => boolean>(),
showRemoveIcon: { type: Boolean, default: undefined },
showDownloadIcon: { type: Boolean, default: undefined },
showPreviewIcon: { type: Boolean, default: undefined },
removeIcon: Function as PropType<(opt: { file: UploadFile }) => VueNode>,
downloadIcon: Function as PropType<(opt: { file: UploadFile }) => VueNode>,
previewIcon: Function as PropType<(opt: { file: UploadFile }) => VueNode>,
showRemoveIcon: booleanType(),
showDownloadIcon: booleanType(),
showPreviewIcon: booleanType(),
removeIcon: functionType<(opt: { file: UploadFile }) => VueNode>(),
downloadIcon: functionType<(opt: { file: UploadFile }) => VueNode>(),
previewIcon: functionType<(opt: { file: UploadFile }) => VueNode>(),
iconRender: Function as PropType<(opt: { file: UploadFile }) => VueNode>,
actionIconRender: Function as PropType<
iconRender: functionType<(opt: { file: UploadFile }) => VueNode>(),
actionIconRender:
functionType<
(opt: {
customIcon: VueNode;
callback: () => void;
prefixCls: string;
title?: string | undefined;
}) => VueNode
>,
itemRender: Function as PropType<ItemRender>,
onPreview: Function as PropType<(file: UploadFile, e: Event) => void>,
onClose: Function as PropType<(file: UploadFile) => void>,
onDownload: Function as PropType<(file: UploadFile) => void>,
progress: Object as PropType<UploadListProgressProps>,
>(),
itemRender: functionType<ItemRender>(),
onPreview: functionType<(file: UploadFile, e: Event) => void>(),
onClose: functionType<(file: UploadFile) => void>(),
onDownload: functionType<(file: UploadFile) => void>(),
progress: objectType<UploadListProgressProps>(),
};
};
@ -93,7 +96,6 @@ export default defineComponent({
const { class: className, style } = attrs;
// This is used for legacy span make scrollHeight the wrong value.
// We will force these to be `display: block` with non `picture-card`
const spanClassName = `${prefixCls}-span`;
const iconNode = iconRender({ file });
let icon = <div class={`${prefixCls}-text-icon`}>{iconNode}</div>;
@ -162,7 +164,7 @@ export default defineComponent({
<span
key="download-delete"
class={[
`${prefixCls}-list-item-card-actions`,
`${prefixCls}-list-item-actions`,
{
picture: listType === 'picture',
},
@ -231,22 +233,21 @@ export default defineComponent({
} else {
message = file.error?.statusText || file.error?.message || locale.uploadError;
}
const iconAndPreview = (
<span class={spanClassName}>
{icon}
{preview}
</span>
);
const dom = (
<div class={infoUploadingClass}>
<div class={`${prefixCls}-list-item-info`}>{iconAndPreview}</div>
{icon}
{preview}
{actions}
{showProgress.value && (
<Transition {...transitionProps.value}>
<div v-show={file.status === 'uploading'} class={`${prefixCls}-list-item-progress`}>
{'percent' in file ? (
<Progress {...progressProps} type="line" percent={file.percent} />
<Progress
{...(progressProps as UploadListProgressProps)}
type="line"
percent={file.percent}
/>
) : null}
</div>
</Transition>
@ -254,7 +255,7 @@ export default defineComponent({
</div>
);
const listContainerNameClass = {
[`${prefixCls}-list-${listType}-container`]: true,
[`${prefixCls}-list-item-container`]: true,
[`${className}`]: !!className,
};
const item =

View File

@ -121,7 +121,7 @@ export default defineComponent({
onClick: () => {
callback();
},
class: `${prefixCls}-list-item-card-actions-btn`,
class: `${prefixCls}-list-item-action`,
};
if (isValidElement(customIcon)) {
return <Button {...btnProps} v-slots={{ icon: () => customIcon }} />;

View File

@ -2,7 +2,7 @@
category: Components
type: Data Entry
title: Upload
cover: https://gw.alipayobjects.com/zos/alicdn/QaeBt_ZMg/Upload.svg
cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*93ymR4RD4S0AAAAAAAAAAAAADrJ8AQ/original
---
Upload file by selecting or dragging.

View File

@ -3,7 +3,7 @@ category: Components
subtitle: 上传
type: 数据录入
title: Upload
cover: https://gw.alipayobjects.com/zos/alicdn/QaeBt_ZMg/Upload.svg
cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*93ymR4RD4S0AAAAAAAAAAAAADrJ8AQ/original
---
文件选择上传和拖拽上传控件。

View File

@ -4,7 +4,15 @@ import type {
} from '../vc-upload/interface';
import type { ProgressProps } from '../progress';
import type { VueNode } from '../_util/type';
import type { ExtractPropTypes, PropType, CSSProperties, ImgHTMLAttributes } from 'vue';
import type { ExtractPropTypes, CSSProperties, ImgHTMLAttributes } from 'vue';
import {
booleanType,
stringType,
functionType,
arrayType,
objectType,
someType,
} from '../_util/type';
export interface FileType extends OriRcFile {
readonly lastModifiedDate: Date;
@ -88,70 +96,67 @@ type BeforeUploadValueType = void | boolean | string | Blob | FileType;
function uploadProps<T = any>() {
return {
capture: [Boolean, String] as PropType<boolean | 'user' | 'environment'>,
type: String as PropType<UploadType>,
capture: someType<boolean | 'user' | 'environment'>([Boolean, String]),
type: stringType<UploadType>(),
name: String,
defaultFileList: Array as PropType<Array<UploadFile<T>>>,
fileList: Array as PropType<Array<UploadFile<T>>>,
action: [String, Function] as PropType<
defaultFileList: arrayType<Array<UploadFile<T>>>(),
fileList: arrayType<Array<UploadFile<T>>>(),
action: someType<
string | ((file: FileType) => string) | ((file: FileType) => PromiseLike<string>)
>,
directory: { type: Boolean, default: undefined },
data: [Object, Function] as PropType<
>([String, Function]),
directory: booleanType(),
data: someType<
| Record<string, unknown>
| ((file: UploadFile<T>) => Record<string, unknown> | Promise<Record<string, unknown>>)
>,
method: String as PropType<'POST' | 'PUT' | 'PATCH' | 'post' | 'put' | 'patch'>,
headers: Object as PropType<HttpRequestHeader>,
showUploadList: {
type: [Boolean, Object] as PropType<boolean | ShowUploadListInterface>,
default: undefined as boolean | ShowUploadListInterface,
},
multiple: { type: Boolean, default: undefined },
>([Object, Function]),
method: stringType<'POST' | 'PUT' | 'PATCH' | 'post' | 'put' | 'patch'>(),
headers: objectType<HttpRequestHeader>(),
showUploadList: someType<boolean | ShowUploadListInterface>(
[Boolean, Object],
undefined as boolean | ShowUploadListInterface,
),
multiple: booleanType(),
accept: String,
beforeUpload: Function as PropType<
beforeUpload:
functionType<
(
file: FileType,
FileList: FileType[],
) => BeforeUploadValueType | Promise<BeforeUploadValueType>
>,
onChange: Function as PropType<(info: UploadChangeParam<UploadFile<T>>) => void>,
'onUpdate:fileList': Function as PropType<
(fileList: UploadChangeParam<UploadFile<T>>['fileList']) => void
>,
onDrop: Function as PropType<(event: DragEvent) => void>,
listType: String as PropType<UploadListType>,
onPreview: Function as PropType<(file: UploadFile<T>) => void>,
onDownload: Function as PropType<(file: UploadFile<T>) => void>,
onReject: Function as PropType<(fileList: FileType[]) => void>,
onRemove: Function as PropType<
(file: UploadFile<T>) => void | boolean | Promise<void | boolean>
>,
>(),
onChange: functionType<(info: UploadChangeParam<UploadFile<T>>) => void>(),
'onUpdate:fileList':
functionType<(fileList: UploadChangeParam<UploadFile<T>>['fileList']) => void>(),
onDrop: functionType<(event: DragEvent) => void>(),
listType: stringType<UploadListType>(),
onPreview: functionType<(file: UploadFile<T>) => void>(),
onDownload: functionType<(file: UploadFile<T>) => void>(),
onReject: functionType<(fileList: FileType[]) => void>(),
onRemove: functionType<(file: UploadFile<T>) => void | boolean | Promise<void | boolean>>(),
/** @deprecated Please use `onRemove` directly */
remove: Function as PropType<(file: UploadFile<T>) => void | boolean | Promise<void | boolean>>,
supportServerRender: { type: Boolean, default: undefined },
disabled: { type: Boolean, default: undefined },
remove: functionType<(file: UploadFile<T>) => void | boolean | Promise<void | boolean>>(),
supportServerRender: booleanType(),
disabled: booleanType(),
prefixCls: String,
customRequest: Function as PropType<(options: RcCustomRequestOptions) => void>,
withCredentials: { type: Boolean, default: undefined },
openFileDialogOnClick: { type: Boolean, default: undefined },
locale: { type: Object as PropType<UploadLocale>, default: undefined as UploadLocale },
customRequest: functionType<(options: RcCustomRequestOptions) => void>(),
withCredentials: booleanType(),
openFileDialogOnClick: booleanType(),
locale: objectType<UploadLocale>(undefined as UploadLocale),
id: String,
previewFile: Function as PropType<PreviewFileHandler>,
previewFile: functionType<PreviewFileHandler>(),
/** @deprecated Please use `beforeUpload` directly */
transformFile: Function as PropType<TransformFileHandler>,
iconRender: Function as PropType<
(opt: { file: UploadFile<T>; listType?: UploadListType }) => VueNode
>,
isImageUrl: Function as PropType<(file: UploadFile) => boolean>,
progress: Object as PropType<UploadListProgressProps>,
itemRender: Function as PropType<ItemRender<T>>,
transformFile: functionType<TransformFileHandler>(),
iconRender:
functionType<(opt: { file: UploadFile<T>; listType?: UploadListType }) => VueNode>(),
isImageUrl: functionType<(file: UploadFile) => boolean>(),
progress: objectType<UploadListProgressProps>(),
itemRender: functionType<ItemRender<T>>(),
/** Config max count of `fileList`. Will replace current one when `maxCount` is 1 */
maxCount: Number,
height: [Number, String],
removeIcon: Function as PropType<(opt: { file: UploadFile }) => VueNode>,
downloadIcon: Function as PropType<(opt: { file: UploadFile }) => VueNode>,
previewIcon: Function as PropType<(opt: { file: UploadFile }) => VueNode>,
height: someType([Number, String]),
removeIcon: functionType<(opt: { file: UploadFile }) => VueNode>(),
downloadIcon: functionType<(opt: { file: UploadFile }) => VueNode>(),
previewIcon: functionType<(opt: { file: UploadFile }) => VueNode>(),
};
}
@ -164,28 +169,27 @@ export interface UploadState<T = any> {
function uploadListProps<T = any>() {
return {
listType: String as PropType<UploadListType>,
onPreview: Function as PropType<(file: UploadFile<T>) => void>,
onDownload: Function as PropType<(file: UploadFile<T>) => void>,
onRemove: Function as PropType<(file: UploadFile<T>) => void | boolean>,
items: Array as PropType<Array<UploadFile<T>>>,
progress: Object as PropType<UploadListProgressProps>,
prefixCls: String as PropType<string>,
showRemoveIcon: { type: Boolean, default: undefined },
showDownloadIcon: { type: Boolean, default: undefined },
showPreviewIcon: { type: Boolean, default: undefined },
removeIcon: Function as PropType<(opt: { file: UploadFile }) => VueNode>,
downloadIcon: Function as PropType<(opt: { file: UploadFile }) => VueNode>,
previewIcon: Function as PropType<(opt: { file: UploadFile }) => VueNode>,
locale: { type: Object as PropType<UploadLocale>, default: undefined as UploadLocale },
previewFile: Function as PropType<PreviewFileHandler>,
iconRender: Function as PropType<
(opt: { file: UploadFile<T>; listType?: UploadListType }) => VueNode
>,
isImageUrl: Function as PropType<(file: UploadFile) => boolean>,
appendAction: Function as PropType<() => VueNode>,
appendActionVisible: { type: Boolean, default: undefined },
itemRender: Function as PropType<ItemRender<T>>,
listType: stringType<UploadListType>(),
onPreview: functionType<(file: UploadFile<T>) => void>(),
onDownload: functionType<(file: UploadFile<T>) => void>(),
onRemove: functionType<(file: UploadFile<T>) => void | boolean>(),
items: arrayType<Array<UploadFile<T>>>(),
progress: objectType<UploadListProgressProps>(),
prefixCls: stringType<string>(),
showRemoveIcon: booleanType(),
showDownloadIcon: booleanType(),
showPreviewIcon: booleanType(),
removeIcon: functionType<(opt: { file: UploadFile }) => VueNode>(),
downloadIcon: functionType<(opt: { file: UploadFile }) => VueNode>(),
previewIcon: functionType<(opt: { file: UploadFile }) => VueNode>(),
locale: objectType<UploadLocale>(undefined as UploadLocale),
previewFile: functionType<PreviewFileHandler>(),
iconRender:
functionType<(opt: { file: UploadFile<T>; listType?: UploadListType }) => VueNode>(),
isImageUrl: functionType<(file: UploadFile) => boolean>(),
appendAction: functionType<() => VueNode>(),
appendActionVisible: booleanType(),
itemRender: functionType<ItemRender<T>>(),
};
}

View File

@ -0,0 +1,76 @@
import type { UploadToken } from '.';
import type { GenerateStyle } from '../../theme/internal';
const genDraggerStyle: GenerateStyle<UploadToken> = token => {
const { componentCls, iconCls } = token;
return {
[`${componentCls}-wrapper`]: {
[`${componentCls}-drag`]: {
position: 'relative',
width: '100%',
height: '100%',
textAlign: 'center',
background: token.colorFillAlter,
border: `${token.lineWidth}px dashed ${token.colorBorder}`,
borderRadius: token.borderRadiusLG,
cursor: 'pointer',
transition: `border-color ${token.motionDurationSlow}`,
[componentCls]: {
padding: `${token.padding}px 0`,
},
[`${componentCls}-btn`]: {
display: 'table',
width: '100%',
height: '100%',
outline: 'none',
},
[`${componentCls}-drag-container`]: {
display: 'table-cell',
verticalAlign: 'middle',
},
[`&:not(${componentCls}-disabled):hover`]: {
borderColor: token.colorPrimaryHover,
},
[`p${componentCls}-drag-icon`]: {
marginBottom: token.margin,
[iconCls]: {
color: token.colorPrimary,
fontSize: token.uploadThumbnailSize,
},
},
[`p${componentCls}-text`]: {
margin: `0 0 ${token.marginXXS}px`,
color: token.colorTextHeading,
fontSize: token.fontSizeLG,
},
[`p${componentCls}-hint`]: {
color: token.colorTextDescription,
fontSize: token.fontSize,
},
// ===================== Disabled =====================
[`&${componentCls}-disabled`]: {
cursor: 'not-allowed',
[`p${componentCls}-drag-icon ${iconCls},
p${componentCls}-text,
p${componentCls}-hint
`]: {
color: token.colorTextDisabled,
},
},
},
},
};
};
export default genDraggerStyle;

View File

@ -1,563 +0,0 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
@upload-prefix-cls: ~'@{ant-prefix}-upload';
@upload-item: ~'@{ant-prefix}-upload-list-item';
@upload-picture-card-size: 104px;
@upload-picture-card-border-style: @border-style-base;
.@{upload-prefix-cls} {
.reset-component();
outline: 0;
p {
margin: 0;
}
&-btn {
display: block;
width: 100%;
outline: none;
}
input[type='file'] {
cursor: pointer;
}
&&-select {
display: inline-block;
}
&&-disabled {
cursor: not-allowed;
}
&&-select-picture-card {
width: @upload-picture-card-size;
height: @upload-picture-card-size;
margin-right: 8px;
margin-bottom: 8px;
text-align: center;
vertical-align: top;
background-color: @background-color-light;
border: @border-width-base dashed @border-color-base;
border-radius: @border-radius-base;
cursor: pointer;
transition: border-color 0.3s;
> .@{upload-prefix-cls} {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
text-align: center;
}
&:hover {
border-color: @primary-color;
.@{upload-prefix-cls}-disabled& {
border-color: @border-color-base;
}
}
}
&&-drag {
position: relative;
width: 100%;
height: 100%;
text-align: center;
background: @background-color-light;
border: @border-width-base dashed @border-color-base;
border-radius: @border-radius-base;
cursor: pointer;
transition: border-color 0.3s;
.@{upload-prefix-cls} {
padding: @padding-md 0;
}
&.@{upload-prefix-cls}-drag-hover:not(.@{upload-prefix-cls}-disabled) {
border-color: @primary-7;
}
&.@{upload-prefix-cls}-disabled {
cursor: not-allowed;
}
.@{upload-prefix-cls}-btn {
display: table;
height: 100%;
}
.@{upload-prefix-cls}-drag-container {
display: table-cell;
vertical-align: middle;
}
&:not(.@{upload-prefix-cls}-disabled):hover {
border-color: @primary-5;
}
p.@{upload-prefix-cls}-drag-icon {
.@{iconfont-css-prefix} {
color: @primary-5;
font-size: 48px;
}
margin-bottom: 20px;
}
p.@{upload-prefix-cls}-text {
margin: 0 0 4px;
color: @heading-color;
font-size: @font-size-lg;
}
p.@{upload-prefix-cls}-hint {
color: @text-color-secondary;
font-size: @font-size-base;
}
.@{iconfont-css-prefix}-plus {
color: @disabled-color;
font-size: 30px;
transition: all 0.3s;
&:hover {
color: @text-color-secondary;
}
}
&:hover .@{iconfont-css-prefix}-plus {
color: @text-color-secondary;
}
}
&-picture-card-wrapper {
.clearfix();
display: inline-block;
width: 100%;
}
}
.@{upload-prefix-cls}-list {
.reset-component();
.clearfix();
line-height: @line-height-base;
// ============================ Item ============================
&-item {
position: relative;
height: @line-height-base * @font-size-base;
margin-top: @margin-xs;
font-size: @font-size-base;
&-name {
display: inline-block;
width: 100%;
padding-left: @font-size-base + 8px;
overflow: hidden;
line-height: @line-height-base;
white-space: nowrap;
text-overflow: ellipsis;
}
&-card-actions {
position: absolute;
right: 0;
&-btn {
opacity: 0;
}
&-btn.@{ant-prefix}-btn-sm {
height: @line-height-base * @font-size-base;
line-height: 1;
vertical-align: top;
}
&.picture {
top: 22px;
line-height: 0;
}
&-btn:focus,
&.picture &-btn {
opacity: 1;
}
.@{iconfont-css-prefix} {
color: @upload-actions-color;
transition: all 0.3s;
}
&:hover .@{iconfont-css-prefix} {
color: @text-color;
}
}
&-info {
height: 100%;
transition: background-color 0.3s;
> span {
display: block;
width: 100%;
height: 100%;
}
.@{iconfont-css-prefix}-loading,
.@{upload-prefix-cls}-text-icon {
.@{iconfont-css-prefix} {
position: absolute;
top: (@font-size-base / 2) - 2px;
color: @text-color-secondary;
font-size: @font-size-base;
}
}
}
&:hover &-info {
background-color: @item-hover-bg;
}
&:hover &-card-actions-btn {
opacity: 1;
}
&-error,
&-error .@{upload-prefix-cls}-text-icon > .@{iconfont-css-prefix},
&-error &-name {
color: @error-color;
}
&-error &-card-actions {
.@{iconfont-css-prefix} {
color: @error-color;
}
&-btn {
opacity: 1;
}
}
&-progress {
position: absolute;
bottom: -12px;
width: 100%;
padding-left: @font-size-base + 12px;
font-size: @font-size-base;
line-height: 0;
}
}
// =================== Picture & Picture Card ===================
&-picture,
&-picture-card {
.@{upload-item} {
position: relative;
height: 66px;
padding: @padding-xs;
border: @border-width-base @upload-picture-card-border-style @border-color-base;
border-radius: @border-radius-base;
&:hover {
background: transparent;
}
&-error {
border-color: @error-color;
}
}
.@{upload-item}-info {
padding: 0;
}
.@{upload-item}:hover .@{upload-item}-info {
background: transparent;
}
.@{upload-item}-uploading {
border-style: dashed;
}
.@{upload-item}-thumbnail {
width: 48px;
height: 48px;
line-height: 60px;
text-align: center;
opacity: 0.8;
.@{iconfont-css-prefix} {
font-size: 26px;
}
}
// Adjust the color of the error icon : https://github.com/ant-design/ant-design/pull/24160
.@{upload-item}-error .@{upload-item}-thumbnail {
.@{iconfont-css-prefix} {
svg path {
&[fill='#e6f7ff'] {
fill: @error-color-deprecated-bg;
}
&[fill='#1890ff'] {
fill: @error-color;
}
}
}
}
.@{upload-item}-icon {
position: absolute;
top: 50%;
left: 50%;
font-size: 26px;
transform: translate(-50%, -50%);
.@{iconfont-css-prefix} {
font-size: 26px;
}
}
.@{upload-item}-image {
max-width: 100%;
}
.@{upload-item}-thumbnail img {
display: block;
width: 48px;
height: 48px;
overflow: hidden;
}
.@{upload-item}-name {
display: inline-block;
box-sizing: border-box;
max-width: 100%;
margin: 0 0 0 8px;
padding-right: 8px;
padding-left: 48px;
overflow: hidden;
line-height: 44px;
white-space: nowrap;
text-overflow: ellipsis;
transition: all 0.3s;
}
.@{upload-item}-uploading .@{upload-item}-name {
margin-bottom: 12px;
}
.@{upload-item}-progress {
bottom: 14px;
width: ~'calc(100% - 24px)';
margin-top: 0;
padding-left: 56px;
}
}
// ======================== Picture Card ========================
&-picture-card {
&-container {
display: inline-block;
width: @upload-picture-card-size;
height: @upload-picture-card-size;
margin: 0 @margin-xs @margin-xs 0;
vertical-align: top;
}
.@{upload-item} {
height: 100%;
margin: 0;
}
.@{upload-item}-info {
position: relative;
height: 100%;
overflow: hidden;
&::before {
position: absolute;
z-index: 1;
width: 100%;
height: 100%;
background-color: fade(@black, 50%);
opacity: 0;
transition: all 0.3s;
content: ' ';
}
}
.@{upload-item}:hover .@{upload-item}-info::before {
opacity: 1;
}
.@{upload-item}-actions {
position: absolute;
top: 50%;
left: 50%;
z-index: 10;
white-space: nowrap;
transform: translate(-50%, -50%);
opacity: 0;
transition: all 0.3s;
.@{iconfont-css-prefix}-eye,
.@{iconfont-css-prefix}-download,
.@{iconfont-css-prefix}-delete {
z-index: 10;
width: 16px;
margin: 0 4px;
color: @text-color-dark;
font-size: 16px;
cursor: pointer;
transition: all 0.3s;
&:hover {
color: @text-color-inverse;
}
}
}
.@{upload-item}-info:hover + .@{upload-item}-actions,
.@{upload-item}-actions:hover {
opacity: 1;
}
.@{upload-item}-thumbnail,
.@{upload-item}-thumbnail img {
position: static;
display: block;
width: 100%;
height: 100%;
object-fit: contain;
}
.@{upload-item}-name {
display: none;
margin: 8px 0 0;
padding: 0;
line-height: @line-height-base;
text-align: center;
}
.@{upload-item}-file + .@{upload-item}-name {
position: absolute;
bottom: 10px;
display: block;
}
.@{upload-item}-uploading {
&.@{upload-item} {
background-color: @background-color-light;
}
.@{upload-item}-info {
height: auto;
&::before,
.@{iconfont-css-prefix}-eye,
.@{iconfont-css-prefix}-delete {
display: none;
}
}
}
.@{upload-item}-progress {
bottom: 32px;
width: calc(100% - 14px);
padding-left: 0;
}
}
// ======================= Picture & Text =======================
&-text,
&-picture {
&-container {
transition: opacity @animation-duration-slow, height @animation-duration-slow;
&::before {
display: table;
width: 0;
height: 0;
content: '';
}
// Don't know why span here, just stretch it
.@{upload-prefix-cls}-span {
display: block;
flex: auto;
}
}
// text & picture no need this additional element.
// But it used for picture-card, let's keep it.
.@{upload-prefix-cls}-span {
display: flex;
align-items: center;
> * {
flex: none;
}
}
.@{upload-item}-name {
flex: auto;
margin: 0;
padding: 0 @padding-xs;
}
.@{upload-item}-card-actions {
position: static;
}
}
// ============================ Text ============================
&-text {
.@{upload-prefix-cls}-text-icon {
.@{iconfont-css-prefix} {
position: static;
}
}
}
// =========================== Motion ===========================
.@{upload-prefix-cls}-animate-inline-appear,
.@{upload-prefix-cls}-animate-inline-enter,
.@{upload-prefix-cls}-animate-inline-leave {
animation-duration: @animation-duration-slow;
animation-fill-mode: @ease-in-out-circ;
}
.@{upload-prefix-cls}-animate-inline-appear,
.@{upload-prefix-cls}-animate-inline-enter {
animation-name: uploadAnimateInlineIn;
}
.@{upload-prefix-cls}-animate-inline-leave {
animation-name: uploadAnimateInlineOut;
}
}
@keyframes uploadAnimateInlineIn {
from {
width: 0;
height: 0;
margin: 0;
padding: 0;
opacity: 0;
}
}
@keyframes uploadAnimateInlineOut {
to {
width: 0;
height: 0;
margin: 0;
padding: 0;
opacity: 0;
}
}
@import './rtl';

View File

@ -0,0 +1,66 @@
import type { FullToken, GenerateStyle } from '../../theme/internal';
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
import genDraggerStyle from './dragger';
import genListStyle from './list';
import genMotionStyle from './motion';
import { genPictureCardStyle, genPictureStyle } from './picture';
import genRtlStyle from './rtl';
import { resetComponent } from '../../_style';
import { genCollapseMotion } from '../../_style/motion';
export interface ComponentToken {}
export interface UploadToken extends FullToken<'Upload'> {
uploadThumbnailSize: number;
uploadProgressOffset: number;
uploadPicCardSize: number;
}
const genBaseStyle: GenerateStyle<UploadToken> = token => {
const { componentCls, colorTextDisabled } = token;
return {
[`${componentCls}-wrapper`]: {
...resetComponent(token),
[componentCls]: {
outline: 0,
"input[type='file']": {
cursor: 'pointer',
},
},
[`${componentCls}-select`]: {
display: 'inline-block',
},
[`${componentCls}-disabled`]: {
color: colorTextDisabled,
cursor: 'not-allowed',
},
},
};
};
// ============================== Export ==============================
export default genComponentStyleHook('Upload', token => {
const { fontSizeHeading3, fontSize, lineHeight, lineWidth, controlHeightLG } = token;
const listItemHeightSM = Math.round(fontSize * lineHeight);
const uploadToken = mergeToken<UploadToken>(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),
];
});

View File

@ -1,8 +0,0 @@
import '../../style/index.less';
import './index.less';
// style dependencies
// deps-lint-skip: form
import '../../button/style';
import '../../progress/style';
import '../../tooltip/style';

View File

@ -0,0 +1,129 @@
import type { UploadToken } from '.';
import type { GenerateStyle } from '../../theme/internal';
import { clearFix, textEllipsis } from '../../_style';
const genListStyle: GenerateStyle<UploadToken> = token => {
const { componentCls, antCls, iconCls, fontSize, lineHeight } = token;
const itemCls = `${componentCls}-list-item`;
const actionsCls = `${itemCls}-actions`;
const actionCls = `${itemCls}-action`;
const listItemHeightSM = Math.round(fontSize * lineHeight);
return {
[`${componentCls}-wrapper`]: {
[`${componentCls}-list`]: {
...clearFix(),
lineHeight: token.lineHeight,
[itemCls]: {
position: 'relative',
height: token.lineHeight * fontSize,
marginTop: token.marginXS,
fontSize,
display: 'flex',
alignItems: 'center',
transition: `background-color ${token.motionDurationSlow}`,
'&:hover': {
backgroundColor: token.controlItemBgHover,
},
[`${itemCls}-name`]: {
...textEllipsis,
padding: `0 ${token.paddingXS}px`,
lineHeight,
flex: 'auto',
transition: `all ${token.motionDurationSlow}`,
},
[actionsCls]: {
[actionCls]: {
opacity: 0,
},
[`${actionCls}${antCls}-btn-sm`]: {
height: listItemHeightSM,
border: 0,
lineHeight: 1,
// FIXME: should not override small button
'> span': {
transform: 'scale(1)',
},
},
[`
${actionCls}:focus,
&.picture ${actionCls}
`]: {
opacity: 1,
},
[iconCls]: {
color: token.colorTextDescription,
transition: `all ${token.motionDurationSlow}`,
},
[`&:hover ${iconCls}`]: {
color: token.colorText,
},
},
[`${componentCls}-icon ${iconCls}`]: {
color: token.colorTextDescription,
fontSize,
},
[`${itemCls}-progress`]: {
position: 'absolute',
bottom: -token.uploadProgressOffset,
width: '100%',
paddingInlineStart: fontSize + token.paddingXS,
fontSize,
lineHeight: 0,
pointerEvents: 'none',
'> div': {
margin: 0,
},
},
},
[`${itemCls}:hover ${actionCls}`]: {
opacity: 1,
color: token.colorText,
},
[`${itemCls}-error`]: {
color: token.colorError,
[`${itemCls}-name, ${componentCls}-icon ${iconCls}`]: {
color: token.colorError,
},
[actionsCls]: {
[`${iconCls}, ${iconCls}:hover`]: {
color: token.colorError,
},
[actionCls]: {
opacity: 1,
},
},
},
[`${componentCls}-list-item-container`]: {
transition: `opacity ${token.motionDurationSlow}, height ${token.motionDurationSlow}`,
// For smooth removing animation
'&::before': {
display: 'table',
width: 0,
height: 0,
content: '""',
},
},
},
},
};
};
export default genListStyle;

View File

@ -0,0 +1,52 @@
import { Keyframes } from '../../_util/cssinjs';
import type { UploadToken } from '.';
import type { GenerateStyle } from '../../theme/internal';
const uploadAnimateInlineIn = new Keyframes('uploadAnimateInlineIn', {
from: {
width: 0,
height: 0,
margin: 0,
padding: 0,
opacity: 0,
},
});
const uploadAnimateInlineOut = new Keyframes('uploadAnimateInlineOut', {
to: {
width: 0,
height: 0,
margin: 0,
padding: 0,
opacity: 0,
},
});
// =========================== Motion ===========================
const genMotionStyle: GenerateStyle<UploadToken> = token => {
const { componentCls } = token;
const inlineCls = `${componentCls}-animate-inline`;
return [
{
[`${componentCls}-wrapper`]: {
[`${inlineCls}-appear, ${inlineCls}-enter, ${inlineCls}-leave`]: {
animationDuration: token.motionDurationSlow,
animationTimingFunction: token.motionEaseInOutCirc,
animationFillMode: 'forwards',
},
[`${inlineCls}-appear, ${inlineCls}-enter`]: {
animationName: uploadAnimateInlineIn,
},
[`${inlineCls}-leave`]: {
animationName: uploadAnimateInlineOut,
},
},
},
uploadAnimateInlineIn,
uploadAnimateInlineOut,
];
};
export default genMotionStyle;

View File

@ -0,0 +1,227 @@
import { TinyColor } from '@ctrl/tinycolor';
import type { UploadToken } from '.';
import type { GenerateStyle } from '../../theme/internal';
import { clearFix, textEllipsis } from '../../_style';
const genPictureStyle: GenerateStyle<UploadToken> = token => {
const { componentCls, iconCls, uploadThumbnailSize, uploadProgressOffset } = token;
const listCls = `${componentCls}-list`;
const itemCls = `${listCls}-item`;
return {
[`${componentCls}-wrapper`]: {
// ${listCls} 增加优先级
[`${listCls}${listCls}-picture, ${listCls}${listCls}-picture-card`]: {
[itemCls]: {
position: 'relative',
height: uploadThumbnailSize + token.lineWidth * 2 + token.paddingXS * 2,
padding: token.paddingXS,
border: `${token.lineWidth}px ${token.lineType} ${token.colorBorder}`,
borderRadius: token.borderRadiusLG,
'&:hover': {
background: 'transparent',
},
[`${itemCls}-thumbnail`]: {
...textEllipsis,
width: uploadThumbnailSize,
height: uploadThumbnailSize,
lineHeight: `${uploadThumbnailSize + token.paddingSM}px`,
textAlign: 'center',
flex: 'none',
[iconCls]: {
fontSize: token.fontSizeHeading2,
color: token.colorPrimary,
},
img: {
display: 'block',
width: '100%',
height: '100%',
overflow: 'hidden',
},
},
[`${itemCls}-progress`]: {
bottom: uploadProgressOffset,
width: `calc(100% - ${token.paddingSM * 2}px)`,
marginTop: 0,
paddingInlineStart: uploadThumbnailSize + token.paddingXS,
},
},
[`${itemCls}-error`]: {
borderColor: token.colorError,
// Adjust the color of the error icon : https://github.com/ant-design/ant-design/pull/24160
[`${itemCls}-thumbnail ${iconCls}`]: {
[`svg path[fill='#e6f7ff']`]: {
fill: token.colorErrorBg,
},
[`svg path[fill='#1890ff']`]: {
fill: token.colorError,
},
},
},
[`${itemCls}-uploading`]: {
borderStyle: 'dashed',
[`${itemCls}-name`]: {
marginBottom: uploadProgressOffset,
},
},
},
},
};
};
const genPictureCardStyle: GenerateStyle<UploadToken> = token => {
const { componentCls, iconCls, fontSizeLG, colorTextLightSolid } = token;
const listCls = `${componentCls}-list`;
const itemCls = `${listCls}-item`;
const uploadPictureCardSize = token.uploadPicCardSize;
return {
[`${componentCls}-wrapper${componentCls}-picture-card-wrapper`]: {
...clearFix(),
display: 'inline-block',
width: '100%',
[`${componentCls}${componentCls}-select`]: {
width: uploadPictureCardSize,
height: uploadPictureCardSize,
marginInlineEnd: token.marginXS,
marginBottom: token.marginXS,
textAlign: 'center',
verticalAlign: 'top',
backgroundColor: token.colorFillAlter,
border: `${token.lineWidth}px dashed ${token.colorBorder}`,
borderRadius: token.borderRadiusLG,
cursor: 'pointer',
transition: `border-color ${token.motionDurationSlow}`,
[`> ${componentCls}`]: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: '100%',
textAlign: 'center',
},
[`&:not(${componentCls}-disabled):hover`]: {
borderColor: token.colorPrimary,
},
},
// list
[`${listCls}${listCls}-picture-card`]: {
[`${listCls}-item-container`]: {
display: 'inline-block',
width: uploadPictureCardSize,
height: uploadPictureCardSize,
marginBlock: `0 ${token.marginXS}px`,
marginInline: `0 ${token.marginXS}px`,
verticalAlign: 'top',
},
'&::after': {
display: 'none',
},
[itemCls]: {
height: '100%',
margin: 0,
'&::before': {
position: 'absolute',
zIndex: 1,
width: `calc(100% - ${token.paddingXS * 2}px)`,
height: `calc(100% - ${token.paddingXS * 2}px)`,
backgroundColor: token.colorBgMask,
opacity: 0,
transition: `all ${token.motionDurationSlow}`,
content: '" "',
},
},
[`${itemCls}:hover`]: {
[`&::before, ${itemCls}-actions`]: {
opacity: 1,
},
},
[`${itemCls}-actions`]: {
position: 'absolute',
insetInlineStart: 0,
zIndex: 10,
width: '100%',
whiteSpace: 'nowrap',
textAlign: 'center',
opacity: 0,
transition: `all ${token.motionDurationSlow}`,
[`${iconCls}-eye, ${iconCls}-download, ${iconCls}-delete`]: {
zIndex: 10,
width: fontSizeLG,
margin: `0 ${token.marginXXS}px`,
fontSize: fontSizeLG,
cursor: 'pointer',
transition: `all ${token.motionDurationSlow}`,
},
},
[`${itemCls}-actions, ${itemCls}-actions:hover`]: {
[`${iconCls}-eye, ${iconCls}-download, ${iconCls}-delete`]: {
color: new TinyColor(colorTextLightSolid).setAlpha(0.65).toRgbString(),
'&:hover': {
color: colorTextLightSolid,
},
},
},
[`${itemCls}-thumbnail, ${itemCls}-thumbnail img`]: {
position: 'static',
display: 'block',
width: '100%',
height: '100%',
objectFit: 'contain',
},
[`${itemCls}-name`]: {
display: 'none',
textAlign: 'center',
},
[`${itemCls}-file + ${itemCls}-name`]: {
position: 'absolute',
bottom: token.margin,
display: 'block',
width: `calc(100% - ${token.paddingXS * 2}px)`,
},
[`${itemCls}-uploading`]: {
[`&${itemCls}`]: {
backgroundColor: token.colorFillAlter,
},
[`&::before, ${iconCls}-eye, ${iconCls}-download, ${iconCls}-delete`]: {
display: 'none',
},
},
[`${itemCls}-progress`]: {
bottom: token.marginXL,
width: `calc(100% - ${token.paddingXS * 2}px)`,
paddingInlineStart: 0,
},
},
},
};
};
export { genPictureStyle, genPictureCardStyle };

View File

@ -1,165 +0,0 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
@upload-prefix-cls: ~'@{ant-prefix}-upload';
@upload-item: ~'@{ant-prefix}-upload-list-item';
.@{upload-prefix-cls} {
&-rtl {
direction: rtl;
}
&&-select-picture-card {
.@{upload-prefix-cls}-rtl& {
margin-right: auto;
margin-left: 8px;
}
}
}
.@{upload-prefix-cls}-list {
&-rtl {
direction: rtl;
}
&-item-list-type-text {
&:hover {
.@{upload-prefix-cls}-list-item-name-icon-count-1 {
.@{upload-prefix-cls}-list-rtl & {
padding-right: 22px;
padding-left: 14px;
}
}
.@{upload-prefix-cls}-list-item-name-icon-count-2 {
.@{upload-prefix-cls}-list-rtl & {
padding-right: 22px;
padding-left: 28px;
}
}
}
}
&-item {
&-name {
.@{upload-prefix-cls}-list-rtl & {
padding-right: @font-size-base + 8px;
padding-left: 0;
}
}
&-name-icon-count-1 {
.@{upload-prefix-cls}-list-rtl & {
padding-left: 14px;
}
}
&-card-actions {
.@{upload-prefix-cls}-list-rtl & {
right: auto;
left: 0;
}
.@{iconfont-css-prefix} {
.@{upload-prefix-cls}-list-rtl & {
padding-right: 0;
padding-left: 5px;
}
}
}
&-info {
.@{upload-prefix-cls}-list-rtl & {
padding: 0 4px 0 12px;
}
}
&-error &-card-actions {
.@{iconfont-css-prefix} {
.@{upload-prefix-cls}-list-rtl & {
padding-right: 0;
padding-left: 5px;
}
}
}
&-progress {
.@{upload-prefix-cls}-list-rtl & {
padding-right: @font-size-base + 12px;
padding-left: 0;
}
}
}
&-picture,
&-picture-card {
.@{upload-item}-info {
padding: 0;
}
.@{upload-item}-thumbnail {
.@{upload-prefix-cls}-list-rtl& {
right: 8px;
left: auto;
}
}
.@{upload-item}-icon {
.@{upload-prefix-cls}-list-rtl& {
right: 50%;
left: auto;
transform: translate(50%, -50%);
}
}
.@{upload-item}-name {
.@{upload-prefix-cls}-list-rtl& {
margin: 0 8px 0 0;
padding-right: 48px;
padding-left: 8px;
}
}
.@{upload-item}-name-icon-count-1 {
.@{upload-prefix-cls}-list-rtl& {
padding-right: 48px;
padding-left: 18px;
}
}
.@{upload-item}-name-icon-count-2 {
.@{upload-prefix-cls}-list-rtl& {
padding-right: 48px;
padding-left: 36px;
}
}
.@{upload-item}-progress {
.@{upload-prefix-cls}-list-rtl& {
padding-right: 0;
padding-left: 0;
}
}
}
&-picture-card {
&-container {
.@{upload-prefix-cls}-list-rtl & {
margin: 0 0 @margin-xs @margin-xs;
}
}
.@{upload-item}-actions {
.@{upload-prefix-cls}-list-rtl& {
right: 50%;
left: auto;
transform: translate(50%, -50%);
}
}
.@{upload-item}-file + .@{upload-item}-name {
.@{upload-prefix-cls}-list-rtl& {
margin: 8px 0 0;
padding: 0;
}
}
}
}

View File

@ -0,0 +1,15 @@
import type { UploadToken } from '.';
import type { GenerateStyle } from '../../theme/internal';
// =========================== Motion ===========================
const genRtlStyle: GenerateStyle<UploadToken> = token => {
const { componentCls } = token;
return {
[`${componentCls}-rtl`]: {
direction: 'rtl',
},
};
};
export default genRtlStyle;