feat: 🎉 Image component (#26296)

* feat: 🎉 Image component

* fix color

* fix style naming and sort api doc order

* remove onPreviewClose doc

* use animation-duration var

* use box and modal-mask mixins

* use box and modal-mask mixins

* improve style

* improve fallback demo src

* add cover

* improve demo

* update demo snapshots

* upgrade rc-image, naming Random -> Reload

* extract modal mask common style, and improve demo

* update snapshots

* fix less var conflict

* upgrade rc-image, adjust maxSize with dark.min.css, improve placeholder demo desc
This commit is contained in:
骗你是小猫咪 2020-08-22 22:02:20 +08:00 committed by GitHub
parent 131b6e0264
commit e3ee4e87aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 435 additions and 37 deletions

View File

@ -29,6 +29,7 @@ Array [
"Form",
"Grid",
"Input",
"Image",
"InputNumber",
"Layout",
"List",

View File

@ -0,0 +1,62 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders ./components/image/demo/basic.md correctly 1`] = `
<div
class="ant-image"
style="width:200px"
>
<img
class="ant-image-img"
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png"
/>
</div>
`;
exports[`renders ./components/image/demo/fallback.md correctly 1`] = `
<div
class="ant-image"
style="width:200px;height:200px"
>
<img
class="ant-image-img"
src="error"
/>
</div>
`;
exports[`renders ./components/image/demo/placeholder.md correctly 1`] = `
Array [
<div
class="ant-image"
style="width:200px"
>
<img
class="ant-image-img"
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png?undefined"
/>
<div
aria-hidden="true"
class="ant-image-placeholder"
>
<div
class="ant-image"
style="width:200px"
>
<img
class="ant-image-img"
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png?x-oss-process=image/blur,r_50,s_50/quality,q_1/resize,m_mfit,h_200,w_200"
/>
</div>
</div>
</div>,
<button
class="ant-btn ant-btn-primary"
style="margin-left:10px"
type="button"
>
<span>
Reload
</span>
</button>,
]
`;

View File

@ -0,0 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Image rtl render component should be rendered correctly in RTL direction 1`] = `
<div
class="ant-image"
>
<img
class="ant-image-img"
/>
</div>
`;

View File

@ -0,0 +1,3 @@
import demoTest from '../../../tests/shared/demoTest';
demoTest('image');

View File

@ -0,0 +1,8 @@
import Image from '..';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
describe('Image', () => {
mountTest(Image);
rtlTest(Image);
});

View File

@ -0,0 +1,29 @@
---
order: 0
title:
zh-CN: 基本用法
en-US: Basic Usage
---
## zh-CN
单击图像可以放大显示。
## en-US
Click the image to zoom in.
```jsx
import { Image } from 'antd';
function ImageDemo() {
return (
<Image
width={200}
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png"
/>
);
}
ReactDOM.render(<ImageDemo />, mountNode);
```

View File

@ -0,0 +1,31 @@
---
order: 1
title:
zh-CN: 容错处理
en-US: Fault tolerant
---
## zh-CN
加载失败显示图像占位符。
## en-US
Load failed to display image placeholder.
```jsx
import { Image } from 'antd';
function ImageDemo() {
return (
<Image
width={200}
height={200}
src="error"
fallback="data:image/png;base64,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"
/>
);
}
ReactDOM.render(<ImageDemo />, mountNode);
```

View File

@ -0,0 +1,50 @@
---
order: 3
title:
zh-CN: 渐进加载
en-US: Progressive Loading
---
## zh-CN
大图使用 placeholder 渐进加载。
## en-US
Progressive when large image loading.
```jsx
import React from 'react';
import { Image, Button } from 'antd';
function ImageDemo() {
const [random, setRandom] = React.useState();
return (
<>
<Image
width={200}
src={`https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png?${random}`}
placeholder={
<Image
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png?x-oss-process=image/blur,r_50,s_50/quality,q_1/resize,m_mfit,h_200,w_200"
width={200}
/>
}
/>
<Button
type="primary"
style={{
marginLeft: 10,
}}
onClick={() => {
setRandom(Date.now());
}}
>
Reload
</Button>
</>
);
}
ReactDOM.render(<ImageDemo />, mountNode);
```

View File

@ -0,0 +1,28 @@
---
category: Components
type: Data Display
title: Image
cols: 2
cover: https://gw.alipayobjects.com/zos/bmw-prod/a77ae075-27de-49c9-9f44-a505f2be07fa.svg
---
Previewable image.
## When To Use
- When you need to display pictures.
- Display when loading a large image or fault tolerant handling when loading fail.
## API
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| alt | Image description | string | - | 4.6.0 |
| fallback | Load failure fault-tolerant src | string | - | 4.6.0 |
| height | Image height | string \| number | - | 4.6.0 |
| placeholder | Load placeholder, use default placeholder when set `true` | ReactNode | - | 4.6.0 |
| preview | Whether to enable the preview | boolean | true | 4.6.0 |
| src | Image path | string | - | 4.6.0 |
| width | Image width | string \| number | - | 4.6.0 |
Other attributes [<img\>](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#Attributes)

View File

@ -0,0 +1,12 @@
import * as React from 'react';
import RcImage, { ImageProps } from 'rc-image';
import { ConfigConsumerProps, ConfigContext } from '../config-provider';
const Image: React.FC<ImageProps> = ({ prefixCls: customizePrefixCls, ...otherProps }) => {
const { getPrefixCls }: ConfigConsumerProps = React.useContext(ConfigContext);
const prefixCls = getPrefixCls('image', customizePrefixCls);
return <RcImage prefixCls={prefixCls} {...otherProps} />;
};
export default Image;

View File

@ -0,0 +1,29 @@
---
category: Components
subtitle: 图片
type: 数据展示
title: Image
cols: 2
cover: https://gw.alipayobjects.com/zos/bmw-prod/a77ae075-27de-49c9-9f44-a505f2be07fa.svg
---
可预览的图片。
## 何时使用
- 需要展示图片时使用。
- 加载大图时显示 loading 或加载失败时容错处理。
## API
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| ----------- | ---------------------------------- | ---------------- | ------ | ----- |
| alt | 图像描述 | string | - | 4.6.0 |
| fallback | 加载失败容错地址 | string | - | 4.6.0 |
| height | 图像高度 | string \| number | - | 4.6.0 |
| placeholder | 加载占位, 为 `true` 时使用默认占位 | ReactNode | - | 4.6.0 |
| preview | 是否开启预览 | boolean | true | 4.6.0 |
| src | 图片地址 | string | - | 4.6.0 |
| width | 图像宽度 | string \| number | - | 4.6.0 |
其他属性见 [<img\>](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#Attributes)

View File

@ -0,0 +1,102 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
@image-prefix-cls: ~'@{ant-prefix}-image';
@image-preview-prefix-cls: ~'@{image-prefix-cls}-preview';
.@{image-prefix-cls} {
position: relative;
display: inline-block;
&-img {
width: 100%;
height: auto;
&-placeholder {
background-color: @image-bg;
background-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMTQuNSAyLjVoLTEzQS41LjUgMCAwIDAgMSAzdjEwYS41LjUgMCAwIDAgLjUuNWgxM2EuNS41IDAgMCAwIC41LS41VjNhLjUuNSAwIDAgMC0uNS0uNXpNNS4yODEgNC43NWExIDEgMCAwIDEgMCAyIDEgMSAwIDAgMSAwLTJ6bTguMDMgNi44M2EuMTI3LjEyNyAwIDAgMS0uMDgxLjAzSDIuNzY5YS4xMjUuMTI1IDAgMCAxLS4wOTYtLjIwN2wyLjY2MS0zLjE1NmEuMTI2LjEyNiAwIDAgMSAuMTc3LS4wMTZsLjAxNi4wMTZMNy4wOCAxMC4wOWwyLjQ3LTIuOTNhLjEyNi4xMjYgMCAwIDEgLjE3Ny0uMDE2bC4wMTUuMDE2IDMuNTg4IDQuMjQ0YS4xMjcuMTI3IDAgMCAxLS4wMi4xNzV6IiBmaWxsPSIjOEM4QzhDIiBmaWxsLXJ1bGU9Im5vbnplcm8iLz48L3N2Zz4=);
background-repeat: no-repeat;
background-position: center center;
background-size: 30%;
}
}
&-placeholder {
.box;
}
&-preview {
height: 100%;
text-align: center;
&-body {
.box;
overflow: hidden;
}
&-img {
max-width: 100%;
max-height: 100%;
vertical-align: middle;
transform: scale3d(1, 1, 1);
cursor: grab;
transition: transform 0.3s @ease-out 0s;
user-select: none;
pointer-events: auto;
&-wrapper {
.box;
transition: transform 0.3s @ease-out 0s;
&::before {
display: inline-block;
width: 1px;
height: 50%;
margin-right: -1px;
content: '';
}
}
}
&-moving {
.@{image-prefix-cls}-preview-img {
cursor: grabbing;
&-wrapper {
transition-duration: 0s;
}
}
}
&-wrap {
z-index: @zindex-image;
}
&-operations {
.reset-component;
position: absolute;
top: 0;
right: 0;
z-index: 1;
display: flex;
flex-direction: row-reverse;
align-items: center;
width: 100%;
color: @image-preview-operation-color;
list-style: none;
background: fade(@modal-mask-bg, 10%);
pointer-events: auto;
&-operation {
margin-left: @control-padding-horizontal;
padding: @control-padding-horizontal;
cursor: pointer;
&-disabled {
color: @image-preview-operation-disabled-color;
pointer-events: none;
}
&:last-of-type {
margin-left: 0;
}
}
&-icon {
font-size: @image-preview-operation-size;
}
}
}
}

View File

@ -0,0 +1,2 @@
import '../../style/index.less';
import './index.less';

View File

@ -70,6 +70,8 @@ export { default as Grid } from './grid';
export { default as Input } from './input';
export { default as Image } from './image';
export { default as InputNumber } from './input-number';
export { default as Layout } from './layout';

View File

@ -9,18 +9,9 @@
width: auto;
margin: 0 auto;
padding-bottom: 24px;
pointer-events: none;
&-wrap {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: @zindex-modal;
overflow: auto;
outline: 0;
-webkit-overflow-scrolling: touch;
}
&-title {
@ -81,7 +72,8 @@
padding: @modal-header-padding;
color: @text-color;
background: @modal-header-bg;
border-bottom: @modal-header-border-width @modal-header-border-style @modal-header-border-color-split;
border-bottom: @modal-header-border-width @modal-header-border-style
@modal-header-border-color-split;
border-radius: @border-radius-base @border-radius-base 0 0;
}
@ -96,7 +88,8 @@
padding: @modal-footer-padding-vertical @modal-footer-padding-horizontal;
text-align: right;
background: @modal-footer-bg;
border-top: @modal-footer-border-width @modal-footer-border-style @modal-footer-border-color-split;
border-top: @modal-footer-border-width @modal-footer-border-style
@modal-footer-border-color-split;
border-radius: 0 0 @border-radius-base @border-radius-base;
button + button {
@ -105,30 +98,6 @@
}
}
&.zoom-enter,
&.zoom-appear {
transform: none; // reset scale avoid mousePosition bug
opacity: 0;
animation-duration: @animation-duration-slow;
user-select: none; // https://github.com/ant-design/ant-design/issues/11777
}
&-mask {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: @zindex-modal-mask;
height: 100%;
background-color: @modal-mask-bg;
filter: ~'alpha(opacity=50)';
&-hidden {
display: none;
}
}
&-open {
overflow: hidden;
}

View File

@ -0,0 +1,7 @@
.box(@position: absolute) {
position: @position;
top: 0;
right: 0;
bottom: 0;
left: 0;
}

View File

@ -9,3 +9,5 @@
@import 'operation-unit';
@import 'typography';
@import 'customize';
@import 'box';
@import 'modal-mask';

View File

@ -0,0 +1,44 @@
@import 'box';
@dialog-mask-modal-prefix-cls: ~'@{ant-prefix}-modal';
@dialog-mask-image-prefix-cls: ~'@{ant-prefix}-image-preview';
.modal-mask() {
.@{dialog-mask-modal-prefix-cls},
.@{dialog-mask-image-prefix-cls} {
pointer-events: none;
}
.@{dialog-mask-modal-prefix-cls}.zoom-enter,
.@{dialog-mask-modal-prefix-cls}.zoom-appear,
.@{dialog-mask-image-prefix-cls}.zoom-enter,
.@{dialog-mask-image-prefix-cls}.zoom-appear {
transform: none; // reset scale avoid mousePosition bug
opacity: 0;
animation-duration: @animation-duration-slow;
user-select: none; // https://github.com/ant-design/ant-design/issues/11777
}
.@{dialog-mask-modal-prefix-cls}-mask,
.@{dialog-mask-image-prefix-cls}-mask {
.box(fixed);
z-index: @zindex-modal-mask;
height: 100%;
background-color: @modal-mask-bg;
filter: ~'alpha(opacity=50)';
&-hidden {
display: none;
}
}
.@{dialog-mask-modal-prefix-cls}-wrap,
.@{dialog-mask-image-prefix-cls}-wrap {
.box(fixed);
overflow: auto;
outline: 0;
-webkit-overflow-scrolling: touch;
}
}
.modal-mask();

View File

@ -343,6 +343,7 @@
@zindex-picker: 1050;
@zindex-popoconfirm: 1060;
@zindex-tooltip: 1070;
@zindex-image: 1080;
// Animation
@animation-duration-slow: 0.3s; // Modal
@ -998,5 +999,8 @@
// ---
@image-size-base: 48px;
@image-font-size-base: 24px;
@image-bg: #ccc;
@image-bg: #f5f5f5;
@image-color: #fff;
@image-preview-operation-size: 18px;
@image-preview-operation-color: @text-color-dark;
@image-preview-operation-disabled-color: fade(@image-preview-operation-color, 45%);

View File

@ -125,6 +125,7 @@
"rc-drawer": "~4.1.0",
"rc-dropdown": "~3.1.2",
"rc-field-form": "~1.10.0",
"rc-image": "~3.0.2",
"rc-input-number": "~6.0.0",
"rc-mentions": "~1.4.0",
"rc-menu": "~8.5.2",
@ -301,7 +302,7 @@
},
{
"path": "./dist/antd.dark.min.css",
"maxSize": "65 kB"
"maxSize": "66 kB"
},
{
"path": "./dist/antd.compact.min.css",

View File

@ -29,6 +29,7 @@ Array [
"Form",
"Grid",
"Input",
"Image",
"InputNumber",
"Layout",
"List",