From 4972383c3c1f1b6a7fc93458e832cb515ec49221 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=B8=85?= Date: Sun, 30 Jun 2019 20:15:44 +0800 Subject: [PATCH] feat: New Components Result (#17244) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * New Components Result antd 又变大了一点 * fix review warning remove classname "view" The class name of status hits the outermost layer * rm fragment * fix build error * update snapshot * add PRESENTED_SVG_DEFAULT * fix doc * fix lint * fix compile error * fix review warning * fix react15 error * add new demo * remove not required code * updated snapshot * api some empty * change svg to image --- .../__snapshots__/index.test.js.snap | 1 + components/index.tsx | 2 + .../__tests__/__snapshots__/demo.test.js.snap | 646 ++++++++++++++++++ components/result/__tests__/demo.test.js | 3 + components/result/__tests__/index.test.js | 50 ++ components/result/demo/basic.md | 106 +++ components/result/demo/complex.md | 47 ++ components/result/demo/customIcon.md | 27 + components/result/index.en-US.md | 23 + components/result/index.tsx | 114 ++++ components/result/index.zh-CN.md | 23 + components/result/noFound.tsx | 284 ++++++++ components/result/serverError.tsx | 329 +++++++++ components/result/style/index.less | 70 ++ components/result/style/index.tsx | 2 + components/result/unauthorized.tsx | 278 ++++++++ tests/__snapshots__/index.test.js.snap | 1 + 17 files changed, 2006 insertions(+) create mode 100644 components/result/__tests__/__snapshots__/demo.test.js.snap create mode 100644 components/result/__tests__/demo.test.js create mode 100644 components/result/__tests__/index.test.js create mode 100644 components/result/demo/basic.md create mode 100644 components/result/demo/complex.md create mode 100644 components/result/demo/customIcon.md create mode 100644 components/result/index.en-US.md create mode 100644 components/result/index.tsx create mode 100644 components/result/index.zh-CN.md create mode 100644 components/result/noFound.tsx create mode 100644 components/result/serverError.tsx create mode 100644 components/result/style/index.less create mode 100644 components/result/style/index.tsx create mode 100644 components/result/unauthorized.tsx diff --git a/components/__tests__/__snapshots__/index.test.js.snap b/components/__tests__/__snapshots__/index.test.js.snap index 079920cab0d6..c5225d948115 100644 --- a/components/__tests__/__snapshots__/index.test.js.snap +++ b/components/__tests__/__snapshots__/index.test.js.snap @@ -46,6 +46,7 @@ Array [ "Progress", "Radio", "Rate", + "Result", "Row", "Select", "Skeleton", diff --git a/components/index.tsx b/components/index.tsx index 4130c5c7fb97..8946a1f977b7 100644 --- a/components/index.tsx +++ b/components/index.tsx @@ -103,6 +103,8 @@ export { default as Radio } from './radio'; export { default as Rate } from './rate'; +export { default as Result } from './result'; + export { default as Row } from './row'; export { default as Select } from './select'; diff --git a/components/result/__tests__/__snapshots__/demo.test.js.snap b/components/result/__tests__/__snapshots__/demo.test.js.snap new file mode 100644 index 000000000000..cb0fac80f350 --- /dev/null +++ b/components/result/__tests__/__snapshots__/demo.test.js.snap @@ -0,0 +1,646 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders ./components/result/demo/basic.md correctly 1`] = ` +
+

+

+ + + + + + + +
+

+

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ 403 +
+
+ Sorry, you are not authorized to access this page. +
+
+ +
+
+
+`; + +exports[`renders ./components/result/demo/complex.md correctly 1`] = ` +
+
+ + + +
+
+ Submission Failed +
+
+ Please check and modify the following information before resubmitting. +
+
+
+

+ The content you submitted has the following error: +

+
+ + + + Your account has been frozen + + Thaw immediately > + +
+
+ + + + Your account is not yet eligible to apply + + Apply immediately > + +
+
+
+
+ + +
+
+`; + +exports[`renders ./components/result/demo/customIcon.md correctly 1`] = ` +
+
+ + + +
+
+ Great, we have done all the operations! +
+
+ +
+
+`; diff --git a/components/result/__tests__/demo.test.js b/components/result/__tests__/demo.test.js new file mode 100644 index 000000000000..bb3506ef1c04 --- /dev/null +++ b/components/result/__tests__/demo.test.js @@ -0,0 +1,3 @@ +import demoTest from '../../../tests/shared/demoTest'; + +demoTest('result'); diff --git a/components/result/__tests__/index.test.js b/components/result/__tests__/index.test.js new file mode 100644 index 000000000000..95614e237228 --- /dev/null +++ b/components/result/__tests__/index.test.js @@ -0,0 +1,50 @@ +import React from 'react'; +import { mount } from 'enzyme'; +import Result from '..'; +import Button from '../../button'; + +describe('Progress', () => { + it('🙂 successPercent should decide the progress status when it exists', () => { + const wrapper = mount( + + Go Console + , + , + ]} + />, + ); + expect(wrapper.find('.anticon-check-circle')).toHaveLength(1); + }); + + it('🙂 different status, different class', () => { + const wrapper = mount(); + expect(wrapper.find('.ant-result-warning')).toHaveLength(1); + + wrapper.setProps({ + status: 'error', + }); + + expect(wrapper.find('.ant-result-error')).toHaveLength(1); + + wrapper.setProps({ + status: '500', + }); + + expect(wrapper.find('.ant-result-500')).toHaveLength(1); + }); + + it('🙂 When status = 404, the icon is an image', () => { + const wrapper = mount(); + expect(wrapper.find('.ant-result-404 .ant-result-image')).toHaveLength(1); + }); + + it('🙂 When extra is undefined, the extra dom is undefined', () => { + const wrapper = mount(); + expect(wrapper.find('.ant-result-extra')).toHaveLength(0); + }); +}); diff --git a/components/result/demo/basic.md b/components/result/demo/basic.md new file mode 100644 index 000000000000..dd459fcf46b4 --- /dev/null +++ b/components/result/demo/basic.md @@ -0,0 +1,106 @@ +--- +order: 0 +title: + zh-CN: 基本 + en-US: Basic +--- + +## zh-CN + +默认支持的各种状态的展示。 + +## en-US + +The display of the default status. + +```jsx +import { Result, Radio, Button } from 'antd'; + +const StatusMap = { + '403': { + title: '403', + subTitle: 'Sorry, you are not authorized to access this page.', + extra: , + }, + '404': { + title: '404', + subTitle: 'Sorry, the page you visited does not exist.', + extra: , + }, + '500': { + title: '404', + subTitle: 'Sorry, the server is wrong.', + extra: , + }, + success: { + title: 'Successfully Purchased Cloud Server ECS!', + subTitle: + 'Order number: 2017182818828182881 Cloud server configuration takes 1-5 minutes, please wait.', + extra: [ + , + , + ], + }, + info: { + title: 'Your operation has been executed', + extra: ( + + ), + }, + error: { + title: 'Submission Failed', + subTitle: 'Please check and modify the following information before resubmitting.', + extra: [ + , + ], + }, + warning: { + title: 'There are some problems with your operation.', + extra: ( + + ), + }, +}; + +const StatusArray = Object.keys(StatusMap); + +class ResultDemo extends React.Component { + state = { + status: '403', + }; + + onChange = e => { + console.log('status checked', e.target.value); + this.setState({ + status: e.target.value, + }); + }; + + render() { + const { status } = this.state; + const resultProps = StatusMap[status]; + return ( +
+

+ + {StatusArray.map(statusItem => ( + {statusItem} + ))} + +

+ +
+ ); + } +} + +ReactDOM.render(, mountNode); +``` diff --git a/components/result/demo/complex.md b/components/result/demo/complex.md new file mode 100644 index 000000000000..1e7716e52c28 --- /dev/null +++ b/components/result/demo/complex.md @@ -0,0 +1,47 @@ +--- +order: 1 +title: + zh-CN: 复杂的例子 + en-US: Complex example +--- + +## zh-CN + +提供更加复杂的反馈。 + +## en-US + +Provide more complex feedback. + +```jsx +import { Result, Button, Icon, Typography } from 'antd'; + +const { Title, Paragraph } = Typography; + +ReactDOM.render( + + Go Console + , + , + ]} + > +
+ The content you submitted has the following error: + + Your account has been frozen{' '} + Thaw immediately > + + + Your account is not yet eligible to apply{' '} + Apply immediately > + +
+
, + mountNode, +); +``` diff --git a/components/result/demo/customIcon.md b/components/result/demo/customIcon.md new file mode 100644 index 000000000000..d375eb756819 --- /dev/null +++ b/components/result/demo/customIcon.md @@ -0,0 +1,27 @@ +--- +order: 2 +title: + zh-CN: 自定义 icon + en-US: Custom icon +--- + +## zh-CN + +自定义 icon。 + +## en-US + +Custom icon. + +```jsx +import { Result, Icon, Button } from 'antd'; + +ReactDOM.render( + } + title="Great, we have done all the operations!" + extra={} + />, + mountNode, +); +``` diff --git a/components/result/index.en-US.md b/components/result/index.en-US.md new file mode 100644 index 000000000000..4d620732d97b --- /dev/null +++ b/components/result/index.en-US.md @@ -0,0 +1,23 @@ +--- +type: Feedback +category: Components +title: Result +cols: 1 +subtitle: +--- + +Used to feed back the results of a series of operational tasks. + +## When To Use + +Use when important operations need to inform the user to process the results and the feedback is more complicated. + +## API + +| Property | Description | Type | Default | Version | +| --- | --- | --- | --- | --- | +| title | title string | ReactNode | - | 3.20.0 | +| subTitle | subTitle string | ReactNode | - | 3.20.0 | +| status | result status,decide icons and colors | `'success' | 'error' | 'info' | 'warning'| '404' | '403' | '500'` | 'info' | 3.20.0 | +| icon | custom back icon | string \| ReactNode | - | 3.20.0 | +| extra | operating area | ReactNode | - | 3.20.0 | diff --git a/components/result/index.tsx b/components/result/index.tsx new file mode 100644 index 000000000000..cc3f3d270df3 --- /dev/null +++ b/components/result/index.tsx @@ -0,0 +1,114 @@ +import * as React from 'react'; +import classnames from 'classnames'; +import { ConfigConsumerProps, ConfigConsumer } from '../config-provider'; +import Icon from '../icon'; +import noFound from './noFound'; +import serverError from './serverError'; +import unauthorized from './unauthorized'; + +export const IconMap = { + success: 'check-circle', + error: 'close-circle', + info: 'exclamation-circle', + warning: 'warning', +}; + +export const ExceptionMap = { + '404': noFound, + '500': serverError, + '403': unauthorized, +}; + +export type ExceptionStatusType = keyof typeof ExceptionMap; +export type ResultStatusType = ExceptionStatusType | keyof typeof IconMap; + +export interface ResultProps { + icon?: React.ReactNode; + status: ResultStatusType; + title?: React.ReactNode; + subTitle?: React.ReactNode; + extra?: React.ReactNode; + prefixCls?: string; + className?: string; + style?: React.CSSProperties; +} + +// ExceptionImageMap keys +const ExceptionStatus = Object.keys(ExceptionMap); + +/** + * render icon + * if ExceptionStatus includes ,render svg image + * else render iconNode + * @param prefixCls + * @param {status, icon} + */ +const renderIcon = (prefixCls: string, { status, icon }: ResultProps) => { + const className = classnames(`${prefixCls}-icon`); + + if (ExceptionStatus.includes(status)) { + const SVGComponent = ExceptionMap[status as ExceptionStatusType]; + return ( +
+ +
+ ); + } + + const iconString: string = IconMap[status as Exclude]; + const iconNode = icon || ; + + return
{iconNode}
; +}; + +const renderExtra = (prefixCls: string, { extra }: ResultProps) => + extra &&
{extra}
; + +export const OriginResult: React.SFC = props => ( + + {({ getPrefixCls }: ConfigConsumerProps) => { + const { + prefixCls: customizePrefixCls, + className, + subTitle, + title, + style, + children, + status, + } = props; + + const prefixCls = getPrefixCls('result', customizePrefixCls); + return ( +
+ {renderIcon(prefixCls, props)} +
{title}
+ {subTitle &&
{subTitle}
} + {children &&
{children}
} + {renderExtra(prefixCls, props)} +
+ ); + }} +
+); + +OriginResult.defaultProps = { + status: 'info', +}; + +// Provide default svg for user access +interface PrivateSVG { + PRESENTED_IMAGE_404: React.ReactNode; + PRESENTED_IMAGE_403: React.ReactNode; + PRESENTED_IMAGE_500: React.ReactNode; +} + +type ResultType = typeof OriginResult & PrivateSVG; + +const Result: ResultType = OriginResult as ResultType; + +ExceptionStatus.forEach((key: ExceptionStatusType) => { + const privateKey = `PRESENTED_IMAGE_${key}` as keyof PrivateSVG; + Result[privateKey] = ExceptionMap[key]; +}); + +export default Result; diff --git a/components/result/index.zh-CN.md b/components/result/index.zh-CN.md new file mode 100644 index 000000000000..30a4d7823388 --- /dev/null +++ b/components/result/index.zh-CN.md @@ -0,0 +1,23 @@ +--- +type: 反馈 +category: Components +title: Result +cols: 1 +subtitle: 结果 +--- + +用于反馈一系列操作任务的处理结果。 + +## 何时使用 + +当有重要操作需告知用户处理结果,且反馈内容较为复杂时使用。 + +## API + +| 参数 | 说明 | 类型 | 默认值 | 版本 | +| --- | --- | --- | --- | --- | +| title | title 文字 | ReactNode | - | 3.20.0 | +| subTitle | subTitle 文字 | ReactNode | - | 3.20.0 | +| status | 结果的状态,决定图标和颜色 | `'success' | 'error' | 'info' | 'warning'| '404' | '403' | '500'` | 'info' | 3.20.0 | +| icon | 自定义 icon | string \| ReactNode | - | 3.20.0 | +| extra | 操作区 | ReactNode | - | 3.20.0 | diff --git a/components/result/noFound.tsx b/components/result/noFound.tsx new file mode 100644 index 000000000000..db22b9705545 --- /dev/null +++ b/components/result/noFound.tsx @@ -0,0 +1,284 @@ +import * as React from 'react'; + +export default () => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); diff --git a/components/result/serverError.tsx b/components/result/serverError.tsx new file mode 100644 index 000000000000..65432ba9e828 --- /dev/null +++ b/components/result/serverError.tsx @@ -0,0 +1,329 @@ +import * as React from 'react'; + +export default () => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); diff --git a/components/result/style/index.less b/components/result/style/index.less new file mode 100644 index 000000000000..63eb8105bf1f --- /dev/null +++ b/components/result/style/index.less @@ -0,0 +1,70 @@ +@import '../../style/themes/default'; +@import '../../style/mixins/index'; + +@result-prefix-cls: ~'@{ant-prefix}-result'; + +.@{result-prefix-cls} { + padding: 48px 32px; + background-color: @component-background; + + // status color + &-success .anticon { + color: @success-color; + } + + &-error .anticon { + color: @error-color; + } + + &-info .anticon { + color: @info-color; + } + + &-warning .anticon { + color: @warning-color; + } + + // Exception Status image + &-image { + width: 250px; + height: 295px; + margin: auto; + } + + &-icon { + margin-bottom: 24px; + text-align: center; + + > .anticon { + font-size: 72px; + } + } + + &-title { + color: @heading-color; + font-size: 24px; + line-height: 1.8; + text-align: center; + } + + &-subtitle { + color: @text-color-secondary; + font-size: 14px; + line-height: 1.6; + text-align: center; + } + + &-extra { + margin-top: 32px; + text-align: center; + > * { + margin-right: 8px; + } + } + + &-content { + margin-top: 24px; + padding: 24px 40px; + background-color: @background-color-light; + } +} diff --git a/components/result/style/index.tsx b/components/result/style/index.tsx new file mode 100644 index 000000000000..3a3ab0de59ac --- /dev/null +++ b/components/result/style/index.tsx @@ -0,0 +1,2 @@ +import '../../style/index.less'; +import './index.less'; diff --git a/components/result/unauthorized.tsx b/components/result/unauthorized.tsx new file mode 100644 index 000000000000..c814ca43010c --- /dev/null +++ b/components/result/unauthorized.tsx @@ -0,0 +1,278 @@ +import * as React from 'react'; + +export default () => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); diff --git a/tests/__snapshots__/index.test.js.snap b/tests/__snapshots__/index.test.js.snap index f3ea00c0294a..33068efdd3a5 100644 --- a/tests/__snapshots__/index.test.js.snap +++ b/tests/__snapshots__/index.test.js.snap @@ -46,6 +46,7 @@ Array [ "Progress", "Radio", "Rate", + "Result", "Row", "Select", "Skeleton",