merge master

This commit is contained in:
zombiej 2019-09-16 11:23:26 +08:00
parent 2ea28af6ed
commit fa8ddb4104
43 changed files with 539 additions and 299 deletions

View File

@ -1,10 +1,10 @@
name: Deploy website name: Deploy website
on: on:
release: release:
actions: types: [published]
- published
branches: branches:
- master - master
jobs: jobs:
build-and-deploy: build-and-deploy:
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

@ -0,0 +1,28 @@
name: Publish to github-package
on:
release:
types: [published]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@master
- uses: actions/setup-node@v1
with:
node-version: '12.x'
registry-url: 'https://npm.pkg.github.com'
- name: install
run: npm install
- name: compile
run: npm run compile
- name: publish
run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -66,7 +66,7 @@ timeline: true
- Descriptions - Descriptions
- 🐞 修复 Descriptions.Item 最后一个宽度计算不正确的问题。[#18568](https://github.com/ant-design/ant-design/pull/18568) - 🐞 修复 Descriptions.Item 最后一个宽度计算不正确的问题。[#18568](https://github.com/ant-design/ant-design/pull/18568)
- 🐞 Description.Item 在渲染时会复用用户提供的 `key`。[#18578](https://github.com/ant-design/ant-design/pull/18578) - 🐞 Description.Item 在渲染时会复用用户提供的 `key`。[#18578](https://github.com/ant-design/ant-design/pull/18578)
- 🐞 修复 Tab 内容宽度在 Safari 下不正确的问题。[#18574](https://github.com/ant-design/ant-design/pull/18574) - 🐞 修复 Tabs 内容宽度在 Safari 下不正确的问题。[#18574](https://github.com/ant-design/ant-design/pull/18574)
- 🐞 修复 Mentions 的 `prefix` 为空字符串时,弹窗位置不正确的问题。[#18576](https://github.com/ant-design/ant-design/pull/18576) - 🐞 修复 Mentions 的 `prefix` 为空字符串时,弹窗位置不正确的问题。[#18576](https://github.com/ant-design/ant-design/pull/18576)
- 🐞 修复 Upload.Dragger 在 `multiple` 为 false 时,仍然可以上传多份文件的问题。[#18580](https://github.com/ant-design/ant-design/pull/18580) - 🐞 修复 Upload.Dragger 在 `multiple` 为 false 时,仍然可以上传多份文件的问题。[#18580](https://github.com/ant-design/ant-design/pull/18580)
- 🐞 修复 `Button[href]` 在 Card `actions` 中样式变形的问题。[#18588](https://github.com/ant-design/ant-design/pull/18588) - 🐞 修复 `Button[href]` 在 Card `actions` 中样式变形的问题。[#18588](https://github.com/ant-design/ant-design/pull/18588)

View File

@ -8,7 +8,7 @@
<div align="center"> <div align="center">
一套企业级的 UI 设计语言和 React 实现 一套企业级 UI 设计语言和 React 组件库
[![CircleCI branch](https://img.shields.io/circleci/project/github/ant-design/ant-design/master.svg?style=flat-square)](https://circleci.com/gh/ant-design/ant-design) [![Codecov](https://img.shields.io/codecov/c/github/ant-design/ant-design/master.svg?style=flat-square)](https://codecov.io/gh/ant-design/ant-design/branch/master) [![npm package](https://img.shields.io/npm/v/antd.svg?style=flat-square)](https://www.npmjs.org/package/antd) [![NPM downloads](http://img.shields.io/npm/dm/antd.svg?style=flat-square)](http://npmjs.com/antd) [![CircleCI branch](https://img.shields.io/circleci/project/github/ant-design/ant-design/master.svg?style=flat-square)](https://circleci.com/gh/ant-design/ant-design) [![Codecov](https://img.shields.io/codecov/c/github/ant-design/ant-design/master.svg?style=flat-square)](https://codecov.io/gh/ant-design/ant-design/branch/master) [![npm package](https://img.shields.io/npm/v/antd.svg?style=flat-square)](https://www.npmjs.org/package/antd) [![NPM downloads](http://img.shields.io/npm/dm/antd.svg?style=flat-square)](http://npmjs.com/antd)

View File

@ -8,7 +8,7 @@
<div align="center"> <div align="center">
An enterprise-class UI design language and React implementation. An enterprise-class UI design language and React UI library.
[![CircleCI branch](https://img.shields.io/circleci/project/github/ant-design/ant-design/master.svg?style=flat-square)](https://circleci.com/gh/ant-design/ant-design) [![Codecov](https://img.shields.io/codecov/c/github/ant-design/ant-design/master.svg?style=flat-square)](https://codecov.io/gh/ant-design/ant-design/branch/master) [![](https://flat.badgen.net/npm/v/antd?icon=npm)](https://www.npmjs.com/package/antd) [![NPM downloads](http://img.shields.io/npm/dm/antd.svg?style=flat-square)](http://npmjs.com/antd) [![CircleCI branch](https://img.shields.io/circleci/project/github/ant-design/ant-design/master.svg?style=flat-square)](https://circleci.com/gh/ant-design/ant-design) [![Codecov](https://img.shields.io/codecov/c/github/ant-design/ant-design/master.svg?style=flat-square)](https://codecov.io/gh/ant-design/ant-design/branch/master) [![](https://flat.badgen.net/npm/v/antd?icon=npm)](https://www.npmjs.com/package/antd) [![NPM downloads](http://img.shields.io/npm/dm/antd.svg?style=flat-square)](http://npmjs.com/antd)

View File

@ -68,10 +68,10 @@ exports[`renders ./components/alert/demo/banner.md correctly 1`] = `
<span <span
class="ant-alert-description" class="ant-alert-description"
/> />
<span <button
class="ant-alert-close-icon" class="ant-alert-close-icon"
role="button"
tabindex="0" tabindex="0"
type="button"
> >
<span <span
aria-label="close" aria-label="close"
@ -93,7 +93,7 @@ exports[`renders ./components/alert/demo/banner.md correctly 1`] = `
/> />
</svg> </svg>
</span> </span>
</span> </button>
</div> </div>
<br /> <br />
<div <div
@ -176,10 +176,10 @@ exports[`renders ./components/alert/demo/closable.md correctly 1`] = `
<span <span
class="ant-alert-description" class="ant-alert-description"
/> />
<span <button
class="ant-alert-close-icon" class="ant-alert-close-icon"
role="button"
tabindex="0" tabindex="0"
type="button"
> >
<span <span
aria-label="close" aria-label="close"
@ -201,7 +201,7 @@ exports[`renders ./components/alert/demo/closable.md correctly 1`] = `
/> />
</svg> </svg>
</span> </span>
</span> </button>
</div> </div>
<div <div
class="ant-alert ant-alert-error ant-alert-with-description ant-alert-no-icon ant-alert-closable" class="ant-alert ant-alert-error ant-alert-with-description ant-alert-no-icon ant-alert-closable"
@ -217,10 +217,10 @@ exports[`renders ./components/alert/demo/closable.md correctly 1`] = `
> >
Error Description Error Description Error Description Error Description Error Description Error Description Error Description Error Description Error Description Error Description Error Description Error Description
</span> </span>
<span <button
class="ant-alert-close-icon" class="ant-alert-close-icon"
role="button"
tabindex="0" tabindex="0"
type="button"
> >
<span <span
aria-label="close" aria-label="close"
@ -242,7 +242,7 @@ exports[`renders ./components/alert/demo/closable.md correctly 1`] = `
/> />
</svg> </svg>
</span> </span>
</span> </button>
</div> </div>
</div> </div>
`; `;
@ -260,17 +260,17 @@ exports[`renders ./components/alert/demo/close-text.md correctly 1`] = `
<span <span
class="ant-alert-description" class="ant-alert-description"
/> />
<span <button
class="ant-alert-close-icon" class="ant-alert-close-icon"
role="button"
tabindex="0" tabindex="0"
type="button"
> >
<span <span
class="ant-alert-close-text" class="ant-alert-close-text"
> >
Close Now Close Now
</span> </span>
</span> </button>
</div> </div>
`; `;
@ -932,10 +932,10 @@ exports[`renders ./components/alert/demo/smooth-closed.md correctly 1`] = `
<span <span
class="ant-alert-description" class="ant-alert-description"
/> />
<span <button
class="ant-alert-close-icon" class="ant-alert-close-icon"
role="button"
tabindex="0" tabindex="0"
type="button"
> >
<span <span
aria-label="close" aria-label="close"
@ -957,7 +957,7 @@ exports[`renders ./components/alert/demo/smooth-closed.md correctly 1`] = `
/> />
</svg> </svg>
</span> </span>
</span> </button>
</div> </div>
<p> <p>
placeholder text here placeholder text here

View File

@ -33,7 +33,7 @@ export interface AlertProps {
/** Additional content of Alert */ /** Additional content of Alert */
description?: React.ReactNode; description?: React.ReactNode;
/** Callback when close Alert */ /** Callback when close Alert */
onClose?: React.MouseEventHandler<HTMLAnchorElement>; onClose?: React.MouseEventHandler<HTMLButtonElement>;
/** Trigger when animation ending of Alert */ /** Trigger when animation ending of Alert */
afterClose?: () => void; afterClose?: () => void;
/** Whether to show icon */ /** Whether to show icon */
@ -70,7 +70,7 @@ export default class Alert extends React.Component<AlertProps, AlertState> {
closed: false, closed: false,
}; };
handleClose = (e: React.MouseEvent<HTMLAnchorElement>) => { handleClose = (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault(); e.preventDefault();
const dom = ReactDOM.findDOMNode(this) as HTMLElement; const dom = ReactDOM.findDOMNode(this) as HTMLElement;
dom.style.height = `${dom.offsetHeight}px`; dom.style.height = `${dom.offsetHeight}px`;
@ -134,14 +134,14 @@ export default class Alert extends React.Component<AlertProps, AlertState> {
); );
const closeIcon = closable ? ( const closeIcon = closable ? (
<span <button
role="button" type="button"
onClick={this.handleClose} onClick={this.handleClose}
className={`${prefixCls}-close-icon`} className={`${prefixCls}-close-icon`}
tabIndex={0} tabIndex={0}
> >
{closeText ? <span className={`${prefixCls}-close-text`}>{closeText}</span> : <Close />} {closeText ? <span className={`${prefixCls}-close-text`}>{closeText}</span> : <Close />}
</span> </button>
) : null; ) : null;
const dataOrAriaProps = getDataOrAriaProps(this.props); const dataOrAriaProps = getDataOrAriaProps(this.props);

View File

@ -74,6 +74,8 @@
overflow: hidden; overflow: hidden;
font-size: @font-size-sm; font-size: @font-size-sm;
line-height: 22px; line-height: 22px;
border: none;
background-color: transparent;
cursor: pointer; cursor: pointer;
.@{iconfont-css-prefix}-close { .@{iconfont-css-prefix}-close {

View File

@ -37,6 +37,7 @@
display: block; display: block;
width: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: cover;
} }
} }

View File

@ -155,6 +155,17 @@ exports[`Button renders Chinese characters correctly 6`] = `
</button> </button>
`; `;
exports[`Button renders Chinese characters correctly 7`] = `
<button
class="ant-btn"
type="button"
>
<span>
按 钮
</span>
</button>
`;
exports[`Button renders correctly 1`] = ` exports[`Button renders correctly 1`] = `
<button <button
class="ant-btn" class="ant-btn"
@ -189,6 +200,17 @@ exports[`Button should merge text if children using variable 1`] = `
</button> </button>
`; `;
exports[`Button should not insert space to link button 1`] = `
<button
class="ant-btn ant-btn-link"
type="button"
>
<span>
按钮
</span>
</button>
`;
exports[`Button should not render as link button when href is undefined 1`] = ` exports[`Button should not render as link button when href is undefined 1`] = `
<button <button
class="ant-btn ant-btn-primary" class="ant-btn ant-btn-primary"
@ -200,6 +222,23 @@ exports[`Button should not render as link button when href is undefined 1`] = `
</button> </button>
`; `;
exports[`Button should render empty button without errors 1`] = `
<Button
block={false}
ghost={false}
htmlType="button"
loading={false}
>
<Wave>
<button
className="ant-btn"
onClick={[Function]}
type="button"
/>
</Wave>
</Button>
`;
exports[`Button should support link button 1`] = ` exports[`Button should support link button 1`] = `
<a <a
class="ant-btn" class="ant-btn"

View File

@ -4,10 +4,15 @@ import renderer from 'react-test-renderer';
import { Search } from '@ant-design/icons'; import { Search } from '@ant-design/icons';
import Button from '..'; import Button from '..';
import mountTest from '../../../tests/shared/mountTest'; import mountTest from '../../../tests/shared/mountTest';
import { sleep } from '../../../tests/utils';
describe('Button', () => { describe('Button', () => {
mountTest(Button); mountTest(Button);
mountTest(() => <Button size="large" />);
mountTest(() => <Button size="small" />);
mountTest(Button.Group); mountTest(Button.Group);
mountTest(() => <Button.Group size="large" />);
mountTest(() => <Button.Group size="small" />);
it('renders correctly', () => { it('renders correctly', () => {
const wrapper = render(<Button>Follow</Button>); const wrapper = render(<Button>Follow</Button>);
@ -45,6 +50,14 @@ describe('Button', () => {
// should insert space while loading // should insert space while loading
const wrapper5 = render(<Button loading>按钮</Button>); const wrapper5 = render(<Button loading>按钮</Button>);
expect(wrapper5).toMatchSnapshot(); expect(wrapper5).toMatchSnapshot();
// should insert space while only one nested element
const wrapper6 = render(
<Button>
<span>按钮</span>
</Button>,
);
expect(wrapper6).toMatchSnapshot();
}); });
it('renders Chinese characters correctly in HOC', () => { it('renders Chinese characters correctly in HOC', () => {
@ -67,6 +80,22 @@ describe('Button', () => {
expect(wrapper.find('.ant-btn').hasClass('ant-btn-two-chinese-chars')).toBe(true); expect(wrapper.find('.ant-btn').hasClass('ant-btn-two-chinese-chars')).toBe(true);
}); });
// https://github.com/ant-design/ant-design/issues/18118
it('should not insert space to link button', () => {
const wrapper = render(<Button type="link">按钮</Button>);
expect(wrapper).toMatchSnapshot();
});
it('should render empty button without errors', () => {
const wrapper = mount(
<Button>
{null}
{undefined}
</Button>,
);
expect(wrapper).toMatchSnapshot();
});
it('have static property for type detecting', () => { it('have static property for type detecting', () => {
const wrapper = mount(<Button>Button Text</Button>); const wrapper = mount(<Button>Button Text</Button>);
// eslint-disable-next-line // eslint-disable-next-line
@ -122,6 +151,17 @@ describe('Button', () => {
expect(wrapper.hasClass('ant-btn-loading')).toBe(false); expect(wrapper.hasClass('ant-btn-loading')).toBe(false);
}); });
it('should not clickable when button is loading', () => {
const onClick = jest.fn();
const wrapper = mount(
<Button loading onClick={onClick}>
button
</Button>,
);
wrapper.simulate('click');
expect(onClick).not.toHaveBeenCalledWith();
});
it('should support link button', () => { it('should support link button', () => {
const wrapper = mount( const wrapper = mount(
<Button target="_blank" href="http://ant.design"> <Button target="_blank" href="http://ant.design">
@ -169,4 +209,27 @@ describe('Button', () => {
expect(wrapper.render()).toMatchSnapshot(); expect(wrapper.render()).toMatchSnapshot();
}); });
it('should support to change loading', async () => {
const wrapper = mount(<Button>Button</Button>);
wrapper.setProps({ loading: true });
wrapper.update();
expect(wrapper.find('.ant-btn-loading').length).toBe(1);
wrapper.setProps({ loading: false });
wrapper.update();
expect(wrapper.find('.ant-btn-loading').length).toBe(0);
wrapper.setProps({ loading: { delay: 50 } });
wrapper.update();
expect(wrapper.find('.ant-btn-loading').length).toBe(0);
await sleep(50);
wrapper.update();
expect(wrapper.find('.ant-btn-loading').length).toBe(1);
wrapper.setProps({ loading: false });
await sleep(50);
wrapper.update();
expect(wrapper.find('.ant-btn-loading').length).toBe(0);
expect(() => {
wrapper.unmount();
}).not.toThrow();
});
}); });

View File

@ -135,16 +135,6 @@ class Button extends React.Component<ButtonProps, ButtonState> {
title: PropTypes.string, title: PropTypes.string,
}; };
static getDerivedStateFromProps(nextProps: ButtonProps, prevState: ButtonState) {
if (nextProps.loading instanceof Boolean) {
return {
...prevState,
loading: nextProps.loading,
};
}
return null;
}
private delayTimeout: number; private delayTimeout: number;
private buttonNode: HTMLElement | null; private buttonNode: HTMLElement | null;
@ -170,8 +160,10 @@ class Button extends React.Component<ButtonProps, ButtonState> {
const { loading } = this.props; const { loading } = this.props;
if (loading && typeof loading !== 'boolean' && loading.delay) { if (loading && typeof loading !== 'boolean' && loading.delay) {
this.delayTimeout = window.setTimeout(() => this.setState({ loading }), loading.delay); this.delayTimeout = window.setTimeout(() => {
} else if (prevProps.loading !== this.props.loading) { this.setState({ loading });
}, loading.delay);
} else if (prevProps.loading !== loading) {
// eslint-disable-next-line react/no-did-update-set-state // eslint-disable-next-line react/no-did-update-set-state
this.setState({ loading }); this.setState({ loading });
} }
@ -218,8 +210,8 @@ class Button extends React.Component<ButtonProps, ButtonState> {
} }
isNeedInserted() { isNeedInserted() {
const { icon, children } = this.props; const { icon, children, type } = this.props;
return React.Children.count(children) === 1 && !icon; return React.Children.count(children) === 1 && !icon && type !== 'link';
} }
renderButton = ({ getPrefixCls, autoInsertSpaceInButton }: ConfigConsumerProps) => { renderButton = ({ getPrefixCls, autoInsertSpaceInButton }: ConfigConsumerProps) => {
@ -261,7 +253,7 @@ class Button extends React.Component<ButtonProps, ButtonState> {
[`${prefixCls}-${shape}`]: shape, [`${prefixCls}-${shape}`]: shape,
[`${prefixCls}-${sizeCls}`]: sizeCls, [`${prefixCls}-${sizeCls}`]: sizeCls,
[`${prefixCls}-icon-only`]: !children && children !== 0 && iconType, [`${prefixCls}-icon-only`]: !children && children !== 0 && iconType,
[`${prefixCls}-loading`]: loading, [`${prefixCls}-loading`]: !!loading,
[`${prefixCls}-background-ghost`]: ghost, [`${prefixCls}-background-ghost`]: ghost,
[`${prefixCls}-two-chinese-chars`]: hasTwoCNChar && autoInsertSpace, [`${prefixCls}-two-chinese-chars`]: hasTwoCNChar && autoInsertSpace,
[`${prefixCls}-block`]: block, [`${prefixCls}-block`]: block,

View File

@ -2,10 +2,16 @@ import React from 'react';
import { mount } from 'enzyme'; import { mount } from 'enzyme';
import ConfigProvider from '..'; import ConfigProvider from '..';
import LocaleProvider from '../../locale-provider'; import LocaleProvider from '../../locale-provider';
import locale from '../../locale/zh_CN'; import zhCN from '../../locale/zh_CN';
import enUS from '../../locale/en_US';
import TimePicker from '../../time-picker'; import TimePicker from '../../time-picker';
import Modal from '../../modal';
describe('ConfigProvider.Locale', () => { describe('ConfigProvider.Locale', () => {
function $$(className) {
return document.body.querySelectorAll(className);
}
it('not throw', () => { it('not throw', () => {
if (process.env.REACT === '15') { if (process.env.REACT === '15') {
return; return;
@ -19,14 +25,54 @@ describe('ConfigProvider.Locale', () => {
); );
}); });
// https://github.com/ant-design/ant-design/issues/18731
it('should not reset locale for Modal', () => {
class App extends React.Component {
state = {
showButton: false,
};
componentDidMount() {
this.setState({
showButton: true,
});
}
openConfirm = () => {
Modal.confirm({
title: 'title',
content: 'Some descriptions',
});
};
render() {
return (
<ConfigProvider locale={zhCN}>
{this.state.showButton ? (
<ConfigProvider locale={enUS}>
<button type="button" onClick={this.openConfirm}>
open
</button>
</ConfigProvider>
) : null}
</ConfigProvider>
);
}
}
const wrapper = mount(<App />);
wrapper.find('button').simulate('click');
expect($$('.ant-btn-primary')[0].textContent).toBe('OK');
});
describe('support legacy LocaleProvider', () => { describe('support legacy LocaleProvider', () => {
function testLocale(wrapper) { function testLocale(wrapper) {
expect(wrapper.find('input').props().placeholder).toBe(locale.TimePicker.placeholder); expect(wrapper.find('input').props().placeholder).toBe(zhCN.TimePicker.placeholder);
} }
it('LocaleProvider', () => { it('LocaleProvider', () => {
const wrapper = mount( const wrapper = mount(
<LocaleProvider locale={locale}> <LocaleProvider locale={zhCN}>
<TimePicker /> <TimePicker />
</LocaleProvider>, </LocaleProvider>,
); );
@ -36,7 +82,7 @@ describe('ConfigProvider.Locale', () => {
it('LocaleProvider > ConfigProvider', () => { it('LocaleProvider > ConfigProvider', () => {
const wrapper = mount( const wrapper = mount(
<LocaleProvider locale={locale}> <LocaleProvider locale={zhCN}>
<ConfigProvider> <ConfigProvider>
<TimePicker /> <TimePicker />
</ConfigProvider> </ConfigProvider>
@ -48,7 +94,7 @@ describe('ConfigProvider.Locale', () => {
it('ConfigProvider > ConfigProvider', () => { it('ConfigProvider > ConfigProvider', () => {
const wrapper = mount( const wrapper = mount(
<ConfigProvider locale={locale}> <ConfigProvider locale={zhCN}>
<ConfigProvider> <ConfigProvider>
<TimePicker /> <TimePicker />
</ConfigProvider> </ConfigProvider>

View File

@ -32,4 +32,4 @@ cols: 1
| label | 内容的描述 | ReactNode | - | 3.19.0 | | label | 内容的描述 | ReactNode | - | 3.19.0 |
| span | 包含列的数量 | number | 1 | 3.19.0 | | span | 包含列的数量 | number | 1 | 3.19.0 |
> span Description.Item 的数量。 span={2} 会占用两个 DescriptionItem 的宽度。 > span Description.Item 的数量。 span={2} 会占用两个 DescriptionItem 的宽度。

View File

@ -4030,20 +4030,16 @@ exports[`renders ./components/form/demo/validate-other.md correctly 1`] = `
<div <div
class="ant-form-item-control-input" class="ant-form-item-control-input"
> >
<div <span
class="dropbox" class=""
> >
<span <div
class="" class="ant-upload ant-upload-drag"
> />
<div <div
class="ant-upload ant-upload-drag" class="ant-upload-list ant-upload-list-text"
/> />
<div </span>
class="ant-upload-list ant-upload-list-text"
/>
</span>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -171,17 +171,15 @@ const Demo = () => {
</Form.Item> </Form.Item>
<Form.Item label="Dragger"> <Form.Item label="Dragger">
<div className="dropbox"> <Form.Item name="dragger" valuePropName="fileList" getValueFromEvent={normFile} noStyle>
<Form.Item name="dragger" valuePropName="fileList" getValueFromEvent={normFile} noStyle> <Upload.Dragger name="files" action="/upload.do">
<Upload.Dragger name="files" action="/upload.do"> <p className="ant-upload-drag-icon">
<p className="ant-upload-drag-icon"> <Inbox />
<Inbox /> </p>
</p> <p className="ant-upload-text">Click or drag file to this area to upload</p>
<p className="ant-upload-text">Click or drag file to this area to upload</p> <p className="ant-upload-hint">Support for a single or bulk upload.</p>
<p className="ant-upload-hint">Support for a single or bulk upload.</p> </Upload.Dragger>
</Upload.Dragger> </Form.Item>
</Form.Item>
</div>
</Form.Item> </Form.Item>
<Form.Item wrapperCol={{ span: 12, offset: 6 }}> <Form.Item wrapperCol={{ span: 12, offset: 6 }}>
@ -195,10 +193,3 @@ const Demo = () => {
ReactDOM.render(<Demo />, mountNode); ReactDOM.render(<Demo />, mountNode);
``` ```
```css
#components-form-demo-validate-other .dropbox {
height: 180px;
line-height: 1.5;
}
```

View File

@ -19,7 +19,7 @@ High performance Form component with data scope management. Including data colle
| Property | Description | Type | Default | | Property | Description | Type | Default |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| component | Set the Form rendering element. Do not create a DOM node for `false` | ComponentType \| false | form | | component | Set the Form rendering element. Do not create a DOM node for `false` | ComponentType \| false | form |
| colon | Configure the default value of `colon` for Form.Item. Indicates whether the colon after the label is displayed | boolean | true | | colon | Configure the default value of `colon` for Form.Item. Indicates whether the colon after the label is displayed (only effective when prop layout is horizontal) | boolean | true |
| fields | Control of form fields through state management (such as redux). Not recommended for non-strong demand. View [example](#components-form-demo-global-state) | [FieldData](#FieldData)\[] | - | | fields | Control of form fields through state management (such as redux). Not recommended for non-strong demand. View [example](#components-form-demo-global-state) | [FieldData](#FieldData)\[] | - |
| form | Form control instance created by `Form.useForm()`. Automatically created when not provided | [FormInstance](#FormInstance) | - | | form | Form control instance created by `Form.useForm()`. Automatically created when not provided | [FormInstance](#FormInstance) | - |
| hideRequiredMark | Hide required mark for all form items | boolean | false | | hideRequiredMark | Hide required mark for all form items | boolean | false |

View File

@ -20,7 +20,7 @@ title: Form
| 参数 | 说明 | 类型 | 默认值 | | 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| component | 设置 Form 渲染元素,为 `false` 则不创建 DOM 节点 | ComponentType \| false | form | | component | 设置 Form 渲染元素,为 `false` 则不创建 DOM 节点 | ComponentType \| false | form |
| colon | 配置 Form.Item 的 `colon` 的默认值。表示是否显示 label 后面的冒号 | boolean | true | | colon | 配置 Form.Item 的 `colon` 的默认值。表示是否显示 label 后面的冒号 (只有在属性 layout 为 horizontal 时有效) | boolean | true |
| fields | 通过状态管理(如 redux控制表单字段如非强需求不推荐使用。查看[示例](#components-form-demo-global-state) | [FieldData](#FieldData)\[] | - | | fields | 通过状态管理(如 redux控制表单字段如非强需求不推荐使用。查看[示例](#components-form-demo-global-state) | [FieldData](#FieldData)\[] | - |
| form | 经 `Form.useForm()` 创建的 form 控制实例,不提供时会自动创建 | [FormInstance](#FormInstance) | - | | form | 经 `Form.useForm()` 创建的 form 控制实例,不提供时会自动创建 | [FormInstance](#FormInstance) | - |
| hideRequiredMark | 隐藏所有表单项的必选标记 | boolean | false | | hideRequiredMark | 隐藏所有表单项的必选标记 | boolean | false |

View File

@ -141,6 +141,7 @@
&-handler-up { &-handler-up {
cursor: pointer; cursor: pointer;
border-top-right-radius: @border-radius-base;
&-inner { &-inner {
top: 50%; top: 50%;
margin-top: -5px; margin-top: -5px;
@ -154,6 +155,7 @@
&-handler-down { &-handler-down {
top: 0; top: 0;
border-top: @border-width-base @border-style-base @border-color-base; border-top: @border-width-base @border-style-base @border-color-base;
border-bottom-right-radius: @border-radius-base;
cursor: pointer; cursor: pointer;
&-inner { &-inner {
top: 50%; top: 50%;

View File

@ -10,7 +10,10 @@ export interface SearchProps extends InputProps {
inputPrefixCls?: string; inputPrefixCls?: string;
onSearch?: ( onSearch?: (
value: string, value: string,
event?: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLInputElement>, event?:
| React.ChangeEvent<HTMLInputElement>
| React.MouseEvent<HTMLElement>
| React.KeyboardEvent<HTMLInputElement>,
) => void; ) => void;
enterButton?: boolean | React.ReactNode; enterButton?: boolean | React.ReactNode;
} }
@ -26,6 +29,16 @@ export default class Search extends React.Component<SearchProps, any> {
this.input = node; this.input = node;
}; };
onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { onChange, onSearch } = this.props;
if (e && e.target && e.type === 'click' && onSearch) {
onSearch((e as React.ChangeEvent<HTMLInputElement>).target.value, e);
}
if (onChange) {
onChange(e);
}
};
onSearch = (e: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLInputElement>) => { onSearch = (e: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLInputElement>) => {
const { onSearch } = this.props; const { onSearch } = this.props;
if (onSearch) { if (onSearch) {
@ -137,6 +150,7 @@ export default class Search extends React.Component<SearchProps, any> {
prefixCls={inputPrefixCls} prefixCls={inputPrefixCls}
addonAfter={this.renderAddonAfter(prefixCls)} addonAfter={this.renderAddonAfter(prefixCls)}
suffix={this.renderSuffix(prefixCls)} suffix={this.renderSuffix(prefixCls)}
onChange={this.onChange}
ref={this.saveInput} ref={this.saveInput}
className={inputClassName} className={inputClassName}
/> />

View File

@ -137,4 +137,19 @@ describe('Input.Search', () => {
expect(wrapper.render()).toMatchSnapshot(); expect(wrapper.render()).toMatchSnapshot();
expect(wrapperWithEnterButton.render()).toMatchSnapshot(); expect(wrapperWithEnterButton.render()).toMatchSnapshot();
}); });
// https://github.com/ant-design/ant-design/issues/18729
it('should trigger onSearch when click clear icon', () => {
const onSearch = jest.fn();
const onChange = jest.fn();
const wrapper = mount(
<Search allowClear defaultValue="value" onSearch={onSearch} onChange={onChange} />,
);
wrapper
.find('.ant-input-clear-icon')
.at(0)
.simulate('click');
expect(onSearch).toHaveBeenLastCalledWith('', expect.anything());
expect(onChange).toHaveBeenCalled();
});
}); });

View File

@ -294,6 +294,7 @@ exports[`Input.Search should support suffix 1`] = `
> >
<Input <Input
className="ant-input-search" className="ant-input-search"
onChange={[Function]}
onPressEnter={[Function]} onPressEnter={[Function]}
prefixCls="ant-input" prefixCls="ant-input"
suffix={ suffix={

View File

@ -73,8 +73,8 @@ export default class LocaleProvider extends React.Component<LocaleProviderProps,
const { locale } = this.props; const { locale } = this.props;
if (prevProps.locale !== locale) { if (prevProps.locale !== locale) {
setMomentLocale(locale); setMomentLocale(locale);
changeConfirmLocale(locale && locale.Modal);
} }
changeConfirmLocale(locale && locale.Modal);
} }
componentWillUnmount() { componentWillUnmount() {

View File

@ -25,5 +25,3 @@ It can also be used as inter-page navigation when it is needed to make the user
| breadcrumb | breadcrumb config | [breadcrumb](https://ant.design/components/breadcrumb-cn/) | - | 3.14.0 | | breadcrumb | breadcrumb config | [breadcrumb](https://ant.design/components/breadcrumb-cn/) | - | 3.14.0 |
| footer | PageHeader's footer, generally used to render TabBar | ReactNode | - | 3.14.0 | | footer | PageHeader's footer, generally used to render TabBar | ReactNode | - | 3.14.0 |
| onBack | back icon click event | `()=>void` | `()=>history.back()` | 3.14.0 | | onBack | back icon click event | `()=>void` | `()=>history.back()` | 3.14.0 |
> breadcrumbs will automatically disappear when configuring back icon.

View File

@ -25,5 +25,3 @@ subtitle: 页头
| breadcrumb | 面包屑的配置 | [breadcrumb](https://ant.design/components/breadcrumb-cn/) | - | 3.14.0 | | breadcrumb | 面包屑的配置 | [breadcrumb](https://ant.design/components/breadcrumb-cn/) | - | 3.14.0 |
| footer | PageHeader 的页脚,一般用于渲染 TabBar | ReactNode | - | 3.14.0 | | footer | PageHeader 的页脚,一般用于渲染 TabBar | ReactNode | - | 3.14.0 |
| onBack | 返回按钮的点击事件 | `()=>void` | `()=>history.back()` | 3.14.0 | | onBack | 返回按钮的点击事件 | `()=>void` | `()=>history.back()` | 3.14.0 |
> 配置返回按钮时breadcrumb 会自动隐藏。

View File

@ -33,6 +33,7 @@ Select component to select value from options.
| dropdownMatchSelectWidth | Whether dropdown's width is same with select. | boolean | true | | | dropdownMatchSelectWidth | Whether dropdown's width is same with select. | boolean | true | |
| dropdownRender | Customize dropdown content | (menuNode: ReactNode, props) => ReactNode | - | 3.11.0 | | dropdownRender | Customize dropdown content | (menuNode: ReactNode, props) => ReactNode | - | 3.11.0 |
| dropdownStyle | style of dropdown menu | object | - | | | dropdownStyle | style of dropdown menu | object | - | |
| dropdownMenuStyle | additional style applied to dropdown menu | object | - | |
| filterOption | If true, filter options by input, if function, filter options against it. The function will receive two arguments, `inputValue` and `option`, if the function returns `true`, the option will be included in the filtered set; Otherwise, it will be excluded. | boolean or function(inputValue, option) | true | | | filterOption | If true, filter options by input, if function, filter options against it. The function will receive two arguments, `inputValue` and `option`, if the function returns `true`, the option will be included in the filtered set; Otherwise, it will be excluded. | boolean or function(inputValue, option) | true | |
| firstActiveValue | Value of action option by default | string\|string\[] | - | | | firstActiveValue | Value of action option by default | string\|string\[] | - | |
| getPopupContainer | Parent Node which the selector should be rendered to. Default to `body`. When position issues happen, try to modify it into scrollable content and position it relative. [Example](https://codesandbox.io/s/4j168r7jw0) | function(triggerNode) | () => document.body | | | getPopupContainer | Parent Node which the selector should be rendered to. Default to `body`. When position issues happen, try to modify it into scrollable content and position it relative. [Example](https://codesandbox.io/s/4j168r7jw0) | function(triggerNode) | () => document.body | |

View File

@ -34,6 +34,7 @@ title: Select
| dropdownMatchSelectWidth | 下拉菜单和选择器同宽 | boolean | true | | | dropdownMatchSelectWidth | 下拉菜单和选择器同宽 | boolean | true | |
| dropdownRender | 自定义下拉框内容 | (menuNode: ReactNode, props) => ReactNode | - | 3.11.0 | | dropdownRender | 自定义下拉框内容 | (menuNode: ReactNode, props) => ReactNode | - | 3.11.0 |
| dropdownStyle | 下拉菜单的 style 属性 | object | - | | | dropdownStyle | 下拉菜单的 style 属性 | object | - | |
| dropdownMenuStyle | dropdown 菜单自定义样式 | object | - | |
| filterOption | 是否根据输入项进行筛选。当其为一个函数时,会接收 `inputValue` `option` 两个参数,当 `option` 符合筛选条件时,应返回 `true`,反之则返回 `false`。 | boolean or function(inputValue, option) | true | | | filterOption | 是否根据输入项进行筛选。当其为一个函数时,会接收 `inputValue` `option` 两个参数,当 `option` 符合筛选条件时,应返回 `true`,反之则返回 `false`。 | boolean or function(inputValue, option) | true | |
| firstActiveValue | 默认高亮的选项 | string\|string\[] | - | | | firstActiveValue | 默认高亮的选项 | string\|string\[] | - | |
| getPopupContainer | 菜单渲染父节点。默认渲染到 body 上,如果你遇到菜单滚动定位问题,试试修改为滚动的区域,并相对其定位。[示例](https://codesandbox.io/s/4j168r7jw0) | Function(triggerNode) | () => document.body | | | getPopupContainer | 菜单渲染父节点。默认渲染到 body 上,如果你遇到菜单滚动定位问题,试试修改为滚动的区域,并相对其定位。[示例](https://codesandbox.io/s/4j168r7jw0) | Function(triggerNode) | () => document.body | |

View File

@ -141,7 +141,7 @@ const columns = [
| sorter | 排序函数,本地排序使用一个函数(参考 [Array.sort](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) 的 compareFunction),需要服务端排序可设为 true | Function\|boolean | - | | | sorter | 排序函数,本地排序使用一个函数(参考 [Array.sort](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) 的 compareFunction),需要服务端排序可设为 true | Function\|boolean | - | |
| sortOrder | 排序的受控属性,外界可用此控制列的排序,可设置为 `'ascend'` `'descend'` `false` | boolean\|string | - | | | sortOrder | 排序的受控属性,外界可用此控制列的排序,可设置为 `'ascend'` `'descend'` `false` | boolean\|string | - | |
| sortDirections | 支持的排序方式,取值为 `'ascend'` `'descend'` | Array | `['ascend', 'descend']` | 3.15.2 | | sortDirections | 支持的排序方式,取值为 `'ascend'` `'descend'` | Array | `['ascend', 'descend']` | 3.15.2 |
| title | 列头显示文字 | ReactNode\|({ sortOrder, filters }) => ReactNode | - | | | title | 列头显示文字(函数用法 `3.10.0` 后支持) | ReactNode\|({ sortOrder, filters }) => ReactNode | - | |
| width | 列宽度([指定了也不生效?](https://github.com/ant-design/ant-design/issues/13825#issuecomment-449889241) | string\|number | - | | | width | 列宽度([指定了也不生效?](https://github.com/ant-design/ant-design/issues/13825#issuecomment-449889241) | string\|number | - | |
| onCell | 设置单元格属性 | Function(record, rowIndex) | - | | | onCell | 设置单元格属性 | Function(record, rowIndex) | - | |
| onFilter | 本地模式下,确定筛选的运行函数 | Function | - | | | onFilter | 本地模式下,确定筛选的运行函数 | Function | - | |

View File

@ -24,19 +24,19 @@ One or more elements can be selected from either column, one click on the proper
| dataSource | Used for setting the source data. The elements that are part of this array will be present the left column. Except the elements whose keys are included in `targetKeys` prop. | [TransferItem](https://git.io/vMM64)\[] | \[] | | | dataSource | Used for setting the source data. The elements that are part of this array will be present the left column. Except the elements whose keys are included in `targetKeys` prop. | [TransferItem](https://git.io/vMM64)\[] | \[] | |
| disabled | Whether disabled transfer | boolean | false | 3.10.0 | | disabled | Whether disabled transfer | boolean | false | 3.10.0 |
| filterOption | A function to determine whether an item should show in search result list | (inputValue, option): boolean | | | | filterOption | A function to determine whether an item should show in search result list | (inputValue, option): boolean | | |
| footer | A function used for rendering the footer. | (props): ReactNode | | | | footer | A function used for rendering the footer. | (props) => ReactNode | | |
| lazy | property of [react-lazy-load](https://github.com/loktar00/react-lazy-load) for lazy rendering items. Turn off it by set to `false`. | object\|boolean | `{ height: 32, offset: 32 }` | | | lazy | property of [react-lazy-load](https://github.com/loktar00/react-lazy-load) for lazy rendering items. Turn off it by set to `false`. | object\|boolean | `{ height: 32, offset: 32 }` | |
| listStyle | A custom CSS style used for rendering the transfer columns. | object | | | | listStyle | A custom CSS style used for rendering the transfer columns. | object | | |
| locale | i18n text including filter, empty text, item unit, etc | { itemUnit: string; itemsUnit: string; searchPlaceholder: string; notFoundContent: ReactNode; } | `{ itemUnit: 'item', itemsUnit: 'items', notFoundContent: 'The list is empty', searchPlaceholder: 'Search here' }` | 3.9.0 | | locale | i18n text including filter, empty text, item unit, etc | { itemUnit: string; itemsUnit: string; searchPlaceholder: string; notFoundContent: ReactNode; } | `{ itemUnit: 'item', itemsUnit: 'items', notFoundContent: 'The list is empty', searchPlaceholder: 'Search here' }` | 3.9.0 |
| operations | A set of operations that are sorted from top to bottom. | string\[] | \['>', '<'] | | | operations | A set of operations that are sorted from top to bottom. | string\[] | \['>', '<'] | |
| operationStyle | A custom CSS style used for rendering the operations column. | object | | 3.6.0 | | operationStyle | A custom CSS style used for rendering the operations column. | object | | 3.6.0 |
| render | The function to generate the item shown on a column. Based on an record (element of the dataSource array), this function should return a React element which is generated from that record. Also, it can return a plain object with `value` and `label`, `label` is a React element and `value` is for title | Function(record) | | | | render | The function to generate the item shown on a column. Based on an record (element of the dataSource array), this function should return a React element which is generated from that record. Also, it can return a plain object with `value` and `label`, `label` is a React element and `value` is for title | (record) => ReactNode | | |
| selectedKeys | A set of keys of selected items. | string\[] | \[] | | | selectedKeys | A set of keys of selected items. | string\[] | \[] | |
| showSearch | If included, a search box is shown on each column. | boolean | false | | | showSearch | If included, a search box is shown on each column. | boolean | false | |
| showSelectAll | Show select all checkbox on the header | boolean | true | 3.18.0 | | showSelectAll | Show select all checkbox on the header | boolean | true | 3.18.0 |
| style | A custom CSS style used for rendering wrapper element. | object | | 3.6.0 | | style | A custom CSS style used for rendering wrapper element. | object | | 3.6.0 |
| targetKeys | A set of keys of elements that are listed on the right column. | string\[] | \[] | | | targetKeys | A set of keys of elements that are listed on the right column. | string\[] | \[] | |
| titles | A set of titles that are sorted from left to right. | string\[] | - | | | titles | A set of titles that are sorted from left to right. | ReactNode\[] | - | |
| onChange | A callback function that is executed when the transfer between columns is complete. | (targetKeys, direction, moveKeys): void | | | | onChange | A callback function that is executed when the transfer between columns is complete. | (targetKeys, direction, moveKeys): void | | |
| onScroll | A callback function which is executed when scroll options list | (direction, event): void | | | | onScroll | A callback function which is executed when scroll options list | (direction, event): void | | |
| onSearch | A callback function which is executed when search field are changed | (direction: 'left'\|'right', value: string): void | - | 3.11.0 | | onSearch | A callback function which is executed when search field are changed | (direction: 'left'\|'right', value: string): void | - | 3.11.0 |

View File

@ -27,18 +27,18 @@ title: Transfer
| dataSource | 数据源,其中的数据将会被渲染到左边一栏中,`targetKeys` 中指定的除外。 | [TransferItem](https://git.io/vMM64)\[] | \[] | | | dataSource | 数据源,其中的数据将会被渲染到左边一栏中,`targetKeys` 中指定的除外。 | [TransferItem](https://git.io/vMM64)\[] | \[] | |
| disabled | 是否禁用 | boolean | false | 3.10.0 | | disabled | 是否禁用 | boolean | false | 3.10.0 |
| filterOption | 接收 `inputValue` `option` 两个参数,当 `option` 符合筛选条件时,应返回 `true`,反之则返回 `false`。 | | (inputValue, option): boolean | | | | filterOption | 接收 `inputValue` `option` 两个参数,当 `option` 符合筛选条件时,应返回 `true`,反之则返回 `false`。 | | (inputValue, option): boolean | | |
| footer | 底部渲染函数 | (props): ReactNode | | | | footer | 底部渲染函数 | (props) => ReactNode | | |
| lazy | Transfer 使用了 [react-lazy-load](https://github.com/loktar00/react-lazy-load) 优化性能,这里可以设置相关参数。设为 `false` 可以关闭懒加载。 | object\|boolean | `{ height: 32, offset: 32 }` | | | lazy | Transfer 使用了 [react-lazy-load](https://github.com/loktar00/react-lazy-load) 优化性能,这里可以设置相关参数。设为 `false` 可以关闭懒加载。 | object\|boolean | `{ height: 32, offset: 32 }` | |
| listStyle | 两个穿梭框的自定义样式 | object | | | | listStyle | 两个穿梭框的自定义样式 | object | | |
| locale | 各种语言 | { itemUnit: string; itemsUnit: string; searchPlaceholder: string; notFoundContent: ReactNode; } | `{ itemUnit: '项', itemsUnit: '项', searchPlaceholder: '请输入搜索内容' }` | 3.9.0 | | locale | 各种语言 | { itemUnit: string; itemsUnit: string; searchPlaceholder: string; notFoundContent: ReactNode; } | `{ itemUnit: '项', itemsUnit: '项', searchPlaceholder: '请输入搜索内容' }` | 3.9.0 |
| operations | 操作文案集合,顺序从上至下 | string\[] | \['>', '<'] | | | operations | 操作文案集合,顺序从上至下 | string\[] | \['>', '<'] | |
| render | 每行数据渲染函数,该函数的入参为 `dataSource` 中的项,返回值为 ReactElement。或者返回一个普通对象其中 `label` 字段为 ReactElement`value` 字段为 title | Function(record) | | | | render | 每行数据渲染函数,该函数的入参为 `dataSource` 中的项,返回值为 ReactElement。或者返回一个普通对象其中 `label` 字段为 ReactElement`value` 字段为 title | (record) => ReactNode | | |
| selectedKeys | 设置哪些项应该被选中 | string\[] | \[] | | | selectedKeys | 设置哪些项应该被选中 | string\[] | \[] | |
| showSearch | 是否显示搜索框 | boolean | false | | | showSearch | 是否显示搜索框 | boolean | false | |
| showSelectAll | 是否展示全选勾选框 | boolean | true | 3.18.0 | | showSelectAll | 是否展示全选勾选框 | boolean | true | 3.18.0 |
| style | 容器的自定义样式 | object | | 3.6.0 | | style | 容器的自定义样式 | object | | 3.6.0 |
| targetKeys | 显示在右侧框数据的 key 集合 | string\[] | \[] | | | targetKeys | 显示在右侧框数据的 key 集合 | string\[] | \[] | |
| titles | 标题集合,顺序从左至右 | string\[] | \['', ''] | | | titles | 标题集合,顺序从左至右 | ReactNode\[] | \['', ''] | |
| onChange | 选项在两栏之间转移时的回调函数 | (targetKeys, direction, moveKeys): void | | | | onChange | 选项在两栏之间转移时的回调函数 | (targetKeys, direction, moveKeys): void | | |
| onScroll | 选项列表滚动时的回调函数 | (direction, event): void | | | | onScroll | 选项列表滚动时的回调函数 | (direction, event): void | | |
| onSearch | 搜索框内容时改变时的回调函数 | (direction: 'left'\|'right', value: string): void | - | 3.11.0 | | onSearch | 搜索框内容时改变时的回调函数 | (direction: 'left'\|'right', value: string): void | - | 3.11.0 |

View File

@ -156,6 +156,8 @@ export interface TreeProps {
onDragOver?: (options: AntTreeNodeMouseEvent) => void; onDragOver?: (options: AntTreeNodeMouseEvent) => void;
onDragLeave?: (options: AntTreeNodeMouseEvent) => void; onDragLeave?: (options: AntTreeNodeMouseEvent) => void;
onDragEnd?: (options: AntTreeNodeMouseEvent) => void; onDragEnd?: (options: AntTreeNodeMouseEvent) => void;
onMouseEnter?: (options: AntTreeNodeMouseEvent) => void;
onMouseLeave?: (options: AntTreeNodeMouseEvent) => void;
onDrop?: (options: AntTreeNodeDropEvent) => void; onDrop?: (options: AntTreeNodeDropEvent) => void;
style?: React.CSSProperties; style?: React.CSSProperties;
showIcon?: boolean; showIcon?: boolean;

View File

@ -33,6 +33,7 @@ Almost anything can be represented in a tree structure. Examples include directo
| loadData | Load data asynchronously | function(node) | - | | | loadData | Load data asynchronously | function(node) | - | |
| loadedKeys | (Controlled) Set loaded tree nodes. Need work with `loadData` | string\[] | \[] | 3.7.0 | | loadedKeys | (Controlled) Set loaded tree nodes. Need work with `loadData` | string\[] | \[] | 3.7.0 |
| multiple | Allows selecting multiple treeNodes | boolean | false | | | multiple | Allows selecting multiple treeNodes | boolean | false | |
| selectable | whether can be selected | boolean | true | |
| selectedKeys | (Controlled) Specifies the keys of the selected treeNodes | string\[] | - | | | selectedKeys | (Controlled) Specifies the keys of the selected treeNodes | string\[] | - | |
| showIcon | Shows the icon before a TreeNode's title. There is no default style; you must set a custom style for it if set to `true` | boolean | false | | | showIcon | Shows the icon before a TreeNode's title. There is no default style; you must set a custom style for it if set to `true` | boolean | false | |
| switcherIcon | customize collapse/expand icon of tree node | React.ReactElement | - | 3.12.0 | | switcherIcon | customize collapse/expand icon of tree node | React.ReactElement | - | 3.12.0 |

View File

@ -34,6 +34,7 @@ subtitle: 树形控件
| loadData | 异步加载数据 | function(node) | - | | | loadData | 异步加载数据 | function(node) | - | |
| loadedKeys | (受控)已经加载的节点,需要配合 `loadData` 使用 | string\[] | \[] | 3.7.0 | | loadedKeys | (受控)已经加载的节点,需要配合 `loadData` 使用 | string\[] | \[] | 3.7.0 |
| multiple | 支持点选多个节点(节点本身) | boolean | false | | | multiple | 支持点选多个节点(节点本身) | boolean | false | |
| selectable | 是否可选中 | boolean | true | |
| selectedKeys | (受控)设置选中的树节点 | string\[] | - | | | selectedKeys | (受控)设置选中的树节点 | string\[] | - | |
| showIcon | 是否展示 TreeNode title 前的图标,没有默认样式,如设置为 true需要自行定义图标相关样式 | boolean | false | | | showIcon | 是否展示 TreeNode title 前的图标,没有默认样式,如设置为 true需要自行定义图标相关样式 | boolean | false | |
| switcherIcon | 自定义树节点的展开/折叠图标 | React.ReactElement | - | 3.12.0 | | switcherIcon | 自定义树节点的展开/折叠图标 | React.ReactElement | - | 3.12.0 |

View File

@ -26,5 +26,5 @@ The complete design pattern will include examples of templates, components (ETC)
We work with engineers to transform design patterns into reusable code that maximizes your productivity and communication efficiency. We work with engineers to transform design patterns into reusable code that maximizes your productivity and communication efficiency.
- [Ant Design Pro](https://pro.ant.design): Out-of-the-box solution with 20+ templates and 10+ business components - [Ant Design Pro](https://pro.ant.design): Out-of-the-box solution with 20+ templates and 10+ business components
- [Ant Design Components](https://ant.design/docs/react/introduce): Ant Design's React implementation is a global component library with 50+ base components - [Ant Design Components](https://ant.design/docs/react/introduce): Ant Design's React UI library is a global component library with 50+ base components
- [Ant Design Library](http://library.ant.design/): Axure resource packs are included with the code to make your prototype look like a visual draft, including templates, components, and more. - [Ant Design Library](http://library.ant.design/): Axure resource packs are included with the code to make your prototype look like a visual draft, including templates, components, and more.

View File

@ -1,46 +1,91 @@
{ {
"name": "antd", "name": "antd",
"version": "4.0.0-alpha.3", "version": "4.0.0-alpha.3",
"title": "Ant Design",
"description": "An enterprise-class UI design language and React components implementation", "description": "An enterprise-class UI design language and React components implementation",
"homepage": "http://ant.design/",
"keywords": [ "keywords": [
"ant", "ant",
"design",
"react",
"react-component",
"component", "component",
"components", "components",
"ui", "design",
"framework", "framework",
"frontend" "frontend",
"react",
"react-component",
"ui"
], ],
"contributors": [ "homepage": "http://ant.design/",
"ant" "bugs": {
], "url": "https://github.com/ant-design/ant-design/issues"
"publishConfig": {
"registry": "https://registry.npmjs.org/"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/ant-design/ant-design" "url": "https://github.com/ant-design/ant-design"
}, },
"bugs": { "license": "MIT",
"url": "https://github.com/ant-design/ant-design/issues" "contributors": [
}, "ant"
"main": "lib/index.js", ],
"module": "es/index.js",
"files": [ "files": [
"dist", "dist",
"lib", "lib",
"es" "es"
], ],
"sideEffects": [
"dist/*",
"es/**/style/*",
"lib/**/style/*",
"*.less"
],
"main": "lib/index.js",
"module": "es/index.js",
"typings": "lib/index.d.ts", "typings": "lib/index.d.ts",
"license": "MIT", "scripts": {
"peerDependencies": { "api-collection": "antd-tools run api-collection",
"react": ">=16.0.0", "authors": "git log --format='%aN <%aE>' | sort -u | grep -v 'users.noreply.github.com' | grep -v 'gitter.im' | grep -v '.local>' | grep -v 'alibaba-inc.com' | grep -v 'alipay.com' | grep -v 'taobao.com' > AUTHORS.txt",
"react-dom": ">=16.0.0" "bundlesize": "bundlesize",
"check-commit": "node ./scripts/check-commit.js",
"compile": "antd-tools run compile",
"predeploy": "antd-tools run clean && npm run site && cp netlify.toml CNAME _site && cp .circleci/config.yml _site",
"deploy": "bisheng gh-pages --push-only",
"deploy:china-mirror": "git checkout gh-pages && git pull origin gh-pages && git push git@gitee.com:ant-design/ant-design.git gh-pages",
"dist": "antd-tools run dist",
"lint": "npm run lint:tsc && npm run lint:script && npm run lint:demo && npm run lint:style && npm run lint:deps",
"lint-fix": "npm run lint-fix:script && npm run lint-fix:demo && npm run lint-fix:style",
"lint-fix:demo": "eslint-tinker ./components/*/demo/*.md",
"lint-fix:script": "npm run lint:script -- --fix",
"lint-fix:style": "npm run lint:style -- --fix",
"lint:demo": "cross-env RUN_ENV=DEMO eslint components/*/demo/*.md --ext '.md'",
"lint:deps": "antd-tools run deps-lint",
"lint:md": "remark components/",
"lint:script": "eslint . --ext '.js,.jsx,.ts,.tsx'",
"lint:style": "stylelint '{site,components}/**/*.less' --syntax less",
"lint:tsc": "npm run tsc",
"pre-publish": "npm run check-commit && npm run test-all",
"prettier": "prettier -c --write '**/*'",
"pretty-quick": "pretty-quick",
"pub": "antd-tools run pub",
"prepublish": "antd-tools run guard",
"site": "cross-env NODE_ICU_DATA=node_modules/full-icu bisheng build --ssr -c ./site/bisheng.config.js && node ./scripts/generateColorLess.js",
"sort": "npx sort-package-json",
"sort-api": "antd-tools run sort-api-table",
"start": "rimraf _site && mkdir _site && node ./scripts/generateColorLess.js && cross-env NODE_ENV=development bisheng start -c ./site/bisheng.config.js",
"start:preact": "node ./scripts/generateColorLess.js && cross-env NODE_ENV=development REACT_ENV=preact bisheng start -c ./site/bisheng.config.js",
"test": "jest --config .jest.js --no-cache",
"test-all": "./scripts/test-all.sh",
"test-node": "jest --config .jest.node.js --no-cache",
"tsc": "tsc"
}, },
"husky": {
"hooks": {
"pre-commit": "pretty-quick --staged"
}
},
"browserslist": [
"last 2 version",
"Firefox ESR",
"> 1%",
"ie >= 9"
],
"dependencies": { "dependencies": {
"@ant-design/create-react-context": "^0.2.4", "@ant-design/create-react-context": "^0.2.4",
"@ant-design/icons": "^4.0.0-alpha.7", "@ant-design/icons": "^4.0.0-alpha.7",
@ -115,7 +160,7 @@
"antd-theme-generator": "^1.1.6", "antd-theme-generator": "^1.1.6",
"babel-eslint": "^10.0.1", "babel-eslint": "^10.0.1",
"babel-plugin-add-react-displayname": "^0.0.5", "babel-plugin-add-react-displayname": "^0.0.5",
"bisheng": "^1.3.0", "bisheng": "^1.3.1-alpha.0",
"bisheng-plugin-antd": "^1.2.1", "bisheng-plugin-antd": "^1.2.1",
"bisheng-plugin-description": "^0.1.4", "bisheng-plugin-description": "^0.1.4",
"bisheng-plugin-react": "^1.1.0", "bisheng-plugin-react": "^1.1.0",
@ -141,7 +186,6 @@
"eslint-plugin-react": "^7.14.2", "eslint-plugin-react": "^7.14.2",
"eslint-tinker": "^0.5.0", "eslint-tinker": "^0.5.0",
"fetch-jsonp": "^1.1.3", "fetch-jsonp": "^1.1.3",
"fs-extra": "^8.1.0",
"full-icu": "^1.3.0", "full-icu": "^1.3.0",
"glob": "^7.1.4", "glob": "^7.1.4",
"husky": "^3.0.2", "husky": "^3.0.2",
@ -168,7 +212,6 @@
"react-copy-to-clipboard": "^5.0.1", "react-copy-to-clipboard": "^5.0.1",
"react-dnd": "^9.0.0", "react-dnd": "^9.0.0",
"react-dnd-html5-backend": "^9.0.0", "react-dnd-html5-backend": "^9.0.0",
"react-document-title": "^2.0.3",
"react-dom": "^16.9.0", "react-dom": "^16.9.0",
"react-github-button": "^0.1.11", "react-github-button": "^0.1.11",
"react-helmet": "^6.0.0-beta", "react-helmet": "^6.0.0-beta",
@ -196,58 +239,13 @@
"xhr2": "^0.2.0", "xhr2": "^0.2.0",
"yaml-front-matter": "^4.0.0" "yaml-front-matter": "^4.0.0"
}, },
"scripts": { "peerDependencies": {
"test": "jest --config .jest.js --no-cache", "react": ">=16.0.0",
"test-node": "jest --config .jest.node.js --no-cache", "react-dom": ">=16.0.0"
"test-all": "./scripts/test-all.sh",
"check-commit": "node ./scripts/check-commit.js",
"lint": "npm run lint:tsc && npm run lint:script && npm run lint:demo && npm run lint:style && npm run lint:deps",
"lint:deps": "antd-tools run deps-lint",
"lint:tsc": "npm run tsc",
"lint:script": "eslint . --ext '.js,.jsx,.ts,.tsx'",
"lint:md": "remark components/",
"lint:demo": "cross-env RUN_ENV=DEMO eslint components/*/demo/*.md --ext '.md'",
"lint:style": "stylelint '{site,components}/**/*.less' --syntax less",
"lint-fix": "npm run lint-fix:script && npm run lint-fix:demo && npm run lint-fix:style",
"lint-fix:script": "npm run lint:script -- --fix",
"lint-fix:demo": "eslint-tinker ./components/*/demo/*.md",
"lint-fix:style": "npm run lint:style -- --fix",
"sort-api": "antd-tools run sort-api-table",
"api-collection": "antd-tools run api-collection",
"dist": "antd-tools run dist",
"bundlesize": "bundlesize",
"compile": "antd-tools run compile",
"tsc": "tsc",
"start": "rimraf _site && mkdir _site && node ./scripts/generateColorLess.js && cross-env NODE_ENV=development bisheng start -c ./site/bisheng.config.js",
"start:preact": "node ./scripts/generateColorLess.js && cross-env NODE_ENV=development REACT_ENV=preact bisheng start -c ./site/bisheng.config.js",
"site": "cross-env NODE_ICU_DATA=node_modules/full-icu bisheng build --ssr -c ./site/bisheng.config.js && node ./scripts/generateColorLess.js",
"predeploy": "antd-tools run clean && npm run site && cp netlify.toml CNAME _site && cp .circleci/config.yml _site",
"deploy": "bisheng gh-pages --push-only",
"deploy:china-mirror": "git checkout gh-pages && git pull origin gh-pages && git push git@gitee.com:ant-design/ant-design.git gh-pages",
"pub": "antd-tools run pub",
"prepublish": "antd-tools run guard",
"pre-publish": "npm run check-commit && npm run test-all",
"authors": "git log --format='%aN <%aE>' | sort -u | grep -v 'users.noreply.github.com' | grep -v 'gitter.im' | grep -v '.local>' | grep -v 'alibaba-inc.com' | grep -v 'alipay.com' | grep -v 'taobao.com' > AUTHORS.txt",
"prettier": "prettier -c --write '**/*'",
"pretty-quick": "pretty-quick"
}, },
"husky": { "publishConfig": {
"hooks": { "registry": "https://registry.npmjs.org/"
"pre-commit": "pretty-quick --staged"
}
}, },
"sideEffects": [
"dist/*",
"es/**/style/*",
"lib/**/style/*",
"*.less"
],
"browserslist": [
"last 2 version",
"Firefox ESR",
"> 1%",
"ie >= 9"
],
"bundlesize": [ "bundlesize": [
{ {
"path": "./dist/antd.min.js", "path": "./dist/antd.min.js",
@ -257,5 +255,6 @@
"path": "./dist/antd.min.css", "path": "./dist/antd.min.css",
"maxSize": "60 kB" "maxSize": "60 kB"
} }
] ],
"title": "Ant Design"
} }

View File

@ -1,7 +1,7 @@
const path = require('path'); const path = require('path');
const yfm = require('yaml-front-matter'); const yfm = require('yaml-front-matter');
const glob = require('glob'); const glob = require('glob');
const fs = require('fs-extra'); const fs = require('fs');
const demoFiles = glob.sync(path.join(process.cwd(), 'components/**/demo/*.md')); const demoFiles = glob.sync(path.join(process.cwd(), 'components/**/demo/*.md'));
// eslint-disable-next-line no-restricted-syntax // eslint-disable-next-line no-restricted-syntax

View File

@ -31,7 +31,7 @@ module.exports = {
'app.home.design-language': 'Design Language', 'app.home.design-language': 'Design Language',
'app.home.solution': 'Solution', 'app.home.solution': 'Solution',
'app.home.components-explain': 'app.home.components-explain':
'Based on the Ant Design language, we have provided a suite of out-of-the-box with high quality for developing and serving enterprise background applications, including the official React implementation and Angular, Vue implementations', 'Based on the Ant Design language, we have provided a suite of out-of-the-box with high quality for developing and serving enterprise background applications, including the official React UI library and Angular, Vue implementations',
'app.home.product-pro-slogan': 'Out-of-the-box front-end / Design solution', 'app.home.product-pro-slogan': 'Out-of-the-box front-end / Design solution',
'app.home.product-mobile-slogan': 'app.home.product-mobile-slogan':
"antd-mobile is the implementation of Ant Design's mobile specification", "antd-mobile is the implementation of Ant Design's mobile specification",

View File

@ -1,14 +1,11 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html {{ htmlAttributes | safe }}>
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta <title>{% if title %}{{ title }}{% else %}{% endif %}</title>
name="description" {% if meta %}{{ meta | safe }}{% endif %}
content="An enterprise-class UI design language and React implementation with a set of high-quality React components, one of best React UI library for enterprises"
/>
<title>{% if title %}{{ title }}{% else %}Ant Design - A UI Design Language{% endif %}</title>
<link <link
rel="icon" rel="icon"
href="https://gw.alipayobjects.com/zos/rmsportal/rlpTLlbMzTNYuZGGCVYM.png" href="https://gw.alipayobjects.com/zos/rmsportal/rlpTLlbMzTNYuZGGCVYM.png"

View File

@ -1,9 +1,10 @@
import React, { Children, cloneElement } from 'react'; import React, { Children, cloneElement } from 'react';
import { FormattedMessage, injectIntl } from 'react-intl'; import { FormattedMessage, injectIntl } from 'react-intl';
import DocumentTitle from 'react-document-title'; import { Helmet } from 'react-helmet';
import { getChildren } from 'jsonml.js/lib/utils'; import { getChildren } from 'jsonml.js/lib/utils';
import { Timeline, Alert, Affix } from 'antd'; import { Timeline, Alert, Affix } from 'antd';
import EditButton from './EditButton'; import EditButton from './EditButton';
import { getMetaDescription } from '../utils';
class Article extends React.Component { class Article extends React.Component {
shouldComponentUpdate(nextProps) { shouldComponentUpdate(nextProps) {
@ -72,58 +73,61 @@ class Article extends React.Component {
const { meta, description } = content; const { meta, description } = content;
const { title, subtitle, filename } = meta; const { title, subtitle, filename } = meta;
const isNotTranslated = locale === 'en-US' && typeof title === 'object'; const isNotTranslated = locale === 'en-US' && typeof title === 'object';
const helmetTitle = `${title[locale] || title} - Ant Design`;
const helmetDesc = getMetaDescription(description);
const contentChild = getMetaDescription(getChildren(content.content));
const metaDesc = helmetDesc || contentChild;
return ( return (
<DocumentTitle title={`${title[locale] || title} - Ant Design`}> /* eslint-disable-next-line */
{/* eslint-disable-next-line */} <article className="markdown" onClick={this.onResourceClick}>
<article className="markdown" onClick={this.onResourceClick}> <Helmet>
{isNotTranslated && ( {helmetTitle && <title>{helmetTitle}</title>}
<Alert {helmetTitle && <meta property="og:title" content={helmetTitle} />}
type="warning" {metaDesc && <meta name="description" content={metaDesc} />}
message={ </Helmet>
<span> {isNotTranslated && (
This article has not been translated yet. Wanna help us out?&nbsp; <Alert
<a href="https://github.com/ant-design/ant-design/issues/1471"> type="warning"
See this issue on GitHub. message={
</a> <span>
</span> This article has not been translated yet. Wanna help us out?&nbsp;
} <a href="https://github.com/ant-design/ant-design/issues/1471">
/> See this issue on GitHub.
)} </a>
<h1> </span>
{title[locale] || title} }
{!subtitle || locale === 'en-US' ? null : <span className="subtitle">{subtitle}</span>} />
<EditButton )}
title={<FormattedMessage id="app.content.edit-page" />} <h1>
filename={filename} {title[locale] || title}
/> {!subtitle || locale === 'en-US' ? null : <span className="subtitle">{subtitle}</span>}
</h1> <EditButton title={<FormattedMessage id="app.content.edit-page" />} filename={filename} />
{!description </h1>
? null {!description
: utils.toReactComponent( ? null
['section', { className: 'markdown' }].concat(getChildren(description)), : utils.toReactComponent(
)} ['section', { className: 'markdown' }].concat(getChildren(description)),
{!content.toc || content.toc.length <= 1 || meta.toc === false ? null : ( )}
<Affix className="toc-affix" offsetTop={16}> {!content.toc || content.toc.length <= 1 || meta.toc === false ? null : (
{utils.toReactComponent( <Affix className="toc-affix" offsetTop={16}>
['ul', { className: 'toc' }].concat(getChildren(content.toc)), {utils.toReactComponent(['ul', { className: 'toc' }].concat(getChildren(content.toc)))}
)} </Affix>
</Affix> )}
)} {this.getArticle(
{this.getArticle( utils.toReactComponent(
utils.toReactComponent( ['section', { className: 'markdown' }].concat(getChildren(content.content)),
['section', { className: 'markdown' }].concat(getChildren(content.content)), ),
), )}
)} {utils.toReactComponent(
{utils.toReactComponent( [
[ 'section',
'section', {
{ className: 'markdown api-container',
className: 'markdown api-container', },
}, ].concat(getChildren(content.api || ['placeholder'])),
].concat(getChildren(content.api || ['placeholder'])), )}
)} </article>
</article>
</DocumentTitle>
); );
} }
} }

View File

@ -1,13 +1,13 @@
import React from 'react'; import React from 'react';
import DocumentTitle from 'react-document-title'; import { Helmet } from 'react-helmet';
import { FormattedMessage, injectIntl } from 'react-intl'; import { FormattedMessage, injectIntl } from 'react-intl';
import classNames from 'classnames'; import classNames from 'classnames';
import { Row, Col, Affix, Tooltip } from 'antd'; import { Row, Col, Affix, Tooltip } from 'antd';
import { getChildren } from 'jsonml.js/lib/utils'; import { getChildren } from 'jsonml.js/lib/utils';
import { AppstoreFilled, Appstore } from '@ant-design/icons';
import Demo from './Demo'; import Demo from './Demo';
import EditButton from './EditButton'; import EditButton from './EditButton';
import Icon from '../Icon'; import { ping, getMetaDescription } from '../utils';
import { ping } from '../utils';
class ComponentDoc extends React.Component { class ComponentDoc extends React.Component {
state = { state = {
@ -107,66 +107,70 @@ class ComponentDoc extends React.Component {
const articleClassName = classNames({ const articleClassName = classNames({
'show-riddle-button': showRiddleButton, 'show-riddle-button': showRiddleButton,
}); });
const helmetTitle = `${subtitle || ''} ${title[locale] || title} - Ant Design`;
const contentChild = getMetaDescription(getChildren(content));
return ( return (
<DocumentTitle title={`${subtitle || ''} ${title[locale] || title} - Ant Design`}> <article className={articleClassName}>
<article className={articleClassName}> <Helmet>
<Affix className="toc-affix" offsetTop={16}> {helmetTitle && <title>{helmetTitle}</title>}
<ul id="demo-toc" className="toc"> {helmetTitle && <meta property="og:title" content={helmetTitle} />}
{jumper} {contentChild && <meta name="description" content={contentChild} />}
</ul> </Helmet>
</Affix> <Affix className="toc-affix" offsetTop={16}>
<section className="markdown"> <ul id="demo-toc" className="toc">
<h1> {jumper}
{title[locale] || title} </ul>
{!subtitle ? null : <span className="subtitle">{subtitle}</span>} </Affix>
<EditButton <section className="markdown">
title={<FormattedMessage id="app.content.edit-page" />} <h1>
filename={filename} {title[locale] || title}
/> {!subtitle ? null : <span className="subtitle">{subtitle}</span>}
</h1> <EditButton
{utils.toReactComponent( title={<FormattedMessage id="app.content.edit-page" />}
['section', { className: 'markdown' }].concat(getChildren(content)), filename={filename}
)} />
<h2> </h1>
<FormattedMessage id="app.component.examples" />
<Tooltip
title={
<FormattedMessage
id={`app.component.examples.${expandAll ? 'collapse' : 'expand'}`}
/>
}
>
<Icon
type={`${expandAll ? 'appstore' : 'appstore-o'}`}
className={expandTriggerClass}
onClick={this.handleExpandToggle}
/>
</Tooltip>
</h2>
</section>
<Row gutter={16}>
<Col
span={isSingleCol ? 24 : 12}
className={isSingleCol ? 'code-boxes-col-1-1' : 'code-boxes-col-2-1'}
>
{leftChildren}
</Col>
{isSingleCol ? null : (
<Col className="code-boxes-col-2-1" span={12}>
{rightChildren}
</Col>
)}
</Row>
{utils.toReactComponent( {utils.toReactComponent(
[ ['section', { className: 'markdown' }].concat(getChildren(content)),
'section',
{
className: 'markdown api-container',
},
].concat(getChildren(doc.api || ['placeholder'])),
)} )}
</article> <h2>
</DocumentTitle> <FormattedMessage id="app.component.examples" />
<Tooltip
title={
<FormattedMessage
id={`app.component.examples.${expandAll ? 'collapse' : 'expand'}`}
/>
}
>
<span className={expandTriggerClass} onClick={this.handleExpandToggle}>
{expandAll ? <AppstoreFilled /> : <Appstore />}
</span>
</Tooltip>
</h2>
</section>
<Row gutter={16}>
<Col
span={isSingleCol ? 24 : 12}
className={isSingleCol ? 'code-boxes-col-1-1' : 'code-boxes-col-2-1'}
>
{leftChildren}
</Col>
{isSingleCol ? null : (
<Col className="code-boxes-col-2-1" span={12}>
{rightChildren}
</Col>
)}
</Row>
{utils.toReactComponent(
[
'section',
{
className: 'markdown api-container',
},
].concat(getChildren(doc.api || ['placeholder'])),
)}
</article>
); );
} }
} }

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { injectIntl } from 'react-intl'; import { injectIntl } from 'react-intl';
import DocumentTitle from 'react-document-title'; import { Helmet } from 'react-helmet';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Banner from './Banner'; import Banner from './Banner';
import Page1 from './Page1'; import Page1 from './Page1';
@ -69,16 +69,17 @@ class Home extends React.Component {
const { isMobile } = this.context; const { isMobile } = this.context;
const childProps = { ...this.props, isMobile, locale: intl.locale }; const childProps = { ...this.props, isMobile, locale: intl.locale };
return ( return (
<DocumentTitle title={`Ant Design - ${intl.formatMessage({ id: 'app.home.slogan' })}`}> <>
<> <style dangerouslySetInnerHTML={{ __html: getStyle() }} /> {/* eslint-disable-line */}
<style dangerouslySetInnerHTML={{ __html: getStyle() }} /> {/* eslint-disable-line */} <Helmet>
<Banner {...childProps} /> <title>{`Ant Design - ${intl.formatMessage({ id: 'app.home.slogan' })}`}</title>
<Page1 {...childProps} /> </Helmet>
<Page2 {...childProps} /> <Banner {...childProps} />
<Page3 {...childProps} /> <Page1 {...childProps} />
<Footer /> <Page2 {...childProps} />
</> <Page3 {...childProps} />
</DocumentTitle> <Footer />
</>
); );
} }
} }

View File

@ -112,10 +112,26 @@ export default class Layout extends React.Component {
render() { render() {
const { children, ...restProps } = this.props; const { children, ...restProps } = this.props;
const { appLocale } = this.state; const { appLocale } = this.state;
const title =
appLocale.locale === 'zh-CN'
? 'Ant Design - 一套企业级 UI 设计语言和 React 组件库'
: 'Ant Design - A UI Design Language and React UI library';
const description =
appLocale.locale === 'zh-CN'
? '基于 Ant Design 设计体系的 React UI 组件库,用于研发企业级中后台产品。'
: 'An enterprise-class UI design language and React UI library with a set of high-quality React components, one of best React UI library for enterprises';
return ( return (
<> <>
<Helmet> <Helmet>
<html lang={appLocale.locale === 'zh-CN' ? 'zh' : 'en'} /> <html lang={appLocale.locale === 'zh-CN' ? 'zh' : 'en'} />
<title>{title}</title>
<meta name="description" content={description} />
<meta property="og:title" content={title} />
<meta property="og:type" content="website" />
<meta
property="og:image"
content="https://gw.alipayobjects.com/zos/rmsportal/rlpTLlbMzTNYuZGGCVYM.png"
/>
</Helmet> </Helmet>
<IntlProvider locale={appLocale.locale} messages={appLocale.messages} defaultLocale="en-US"> <IntlProvider locale={appLocale.locale} messages={appLocale.messages} defaultLocale="en-US">
<ConfigProvider locale={appLocale.locale === 'zh-CN' ? zhCN : null}> <ConfigProvider locale={appLocale.locale === 'zh-CN' ? zhCN : null}>

View File

@ -1,3 +1,6 @@
import flattenDeep from 'lodash/flattenDeep';
import flatten from 'lodash/flatten';
export function getMenuItems(moduleData, locale, categoryOrder, typeOrder) { export function getMenuItems(moduleData, locale, categoryOrder, typeOrder) {
const menuMeta = moduleData.map(item => item.meta).filter(meta => !meta.skip); const menuMeta = moduleData.map(item => item.meta).filter(meta => !meta.skip);
@ -109,3 +112,27 @@ export function loadScript(src) {
document.head.appendChild(script); document.head.appendChild(script);
}); });
} }
export function getMetaDescription(jml) {
const COMMON_TAGS = ['h1', 'h2', 'h3', 'p', 'img', 'a', 'code', 'strong'];
if (!Array.isArray(jml)) return '';
const paragraph = flattenDeep(
jml
.filter(item => {
if (Array.isArray(item)) {
const [tag] = item;
return tag === 'p';
}
return false;
})
// ['p', ['code', 'aa'], 'bb'] => ['p', 'aabb']
.map(item => {
const [tag, ...others] = flatten(item);
const content = others
.filter(other => typeof other === 'string' && !COMMON_TAGS.includes(other))
.join('');
return [tag, content];
}),
).find(p => p && typeof p === 'string' && !COMMON_TAGS.includes(p));
return paragraph;
}