Browse Source

merge master

pull/18817/head
zombiej 5 years ago
parent
commit
fa8ddb4104
  1. 4
      .github/workflows/deploy-site.yml
  2. 28
      .github/workflows/publish-to-github-package.yml
  3. 2
      CHANGELOG.zh-CN.md
  4. 2
      README-zh_CN.md
  5. 2
      README.md
  6. 30
      components/alert/__tests__/__snapshots__/demo.test.js.snap
  7. 10
      components/alert/index.tsx
  8. 2
      components/alert/style/index.less
  9. 1
      components/avatar/style/index.less
  10. 39
      components/button/__tests__/__snapshots__/index.test.js.snap
  11. 63
      components/button/__tests__/index.test.js
  12. 22
      components/button/button.tsx
  13. 56
      components/config-provider/__tests__/locale.test.js
  14. 2
      components/descriptions/index.zh-CN.md
  15. 22
      components/form/__tests__/__snapshots__/demo.test.js.snap
  16. 27
      components/form/demo/validate-other.md
  17. 2
      components/form/index.en-US.md
  18. 2
      components/form/index.zh-CN.md
  19. 2
      components/input-number/style/index.less
  20. 16
      components/input/Search.tsx
  21. 15
      components/input/__tests__/Search.test.js
  22. 1
      components/input/__tests__/__snapshots__/index.test.js.snap
  23. 2
      components/locale-provider/index.tsx
  24. 2
      components/page-header/index.en-US.md
  25. 2
      components/page-header/index.zh-CN.md
  26. 1
      components/select/index.en-US.md
  27. 1
      components/select/index.zh-CN.md
  28. 2
      components/table/index.zh-CN.md
  29. 6
      components/transfer/index.en-US.md
  30. 6
      components/transfer/index.zh-CN.md
  31. 2
      components/tree/Tree.tsx
  32. 1
      components/tree/index.en-US.md
  33. 1
      components/tree/index.zh-CN.md
  34. 2
      docs/spec/overview.en-US.md
  35. 149
      package.json
  36. 2
      scripts/check-demo.js
  37. 2
      site/theme/en-US.js
  38. 9
      site/theme/static/template.html
  39. 108
      site/theme/template/Content/Article.jsx
  40. 120
      site/theme/template/Content/ComponentDoc.jsx
  41. 23
      site/theme/template/Home/index.jsx
  42. 16
      site/theme/template/Layout/index.jsx
  43. 27
      site/theme/template/utils.jsx

4
.github/workflows/deploy-site.yml

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

28
.github/workflows/publish-to-github-package.yml

@ -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 }}

2
CHANGELOG.zh-CN.md

@ -66,7 +66,7 @@ timeline: true
- Descriptions
- 🐞 修复 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)
- 🐞 修复 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)
- 🐞 修复 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)

2
README-zh_CN.md

@ -8,7 +8,7 @@
<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)

2
README.md

@ -8,7 +8,7 @@
<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)

30
components/alert/__tests__/__snapshots__/demo.test.js.snap

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

10
components/alert/index.tsx

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

2
components/alert/style/index.less

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

1
components/avatar/style/index.less

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

39
components/button/__tests__/__snapshots__/index.test.js.snap

@ -155,6 +155,17 @@ exports[`Button renders Chinese characters correctly 6`] = `
</button>
`;
exports[`Button renders Chinese characters correctly 7`] = `
<button
class="ant-btn"
type="button"
>
<span>
按 钮
</span>
</button>
`;
exports[`Button renders correctly 1`] = `
<button
class="ant-btn"
@ -189,6 +200,17 @@ exports[`Button should merge text if children using variable 1`] = `
</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`] = `
<button
class="ant-btn ant-btn-primary"
@ -200,6 +222,23 @@ exports[`Button should not render as link button when href is undefined 1`] = `
</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`] = `
<a
class="ant-btn"

63
components/button/__tests__/index.test.js

@ -4,10 +4,15 @@ import renderer from 'react-test-renderer';
import { Search } from '@ant-design/icons';
import Button from '..';
import mountTest from '../../../tests/shared/mountTest';
import { sleep } from '../../../tests/utils';
describe('Button', () => {
mountTest(Button);
mountTest(() => <Button size="large" />);
mountTest(() => <Button size="small" />);
mountTest(Button.Group);
mountTest(() => <Button.Group size="large" />);
mountTest(() => <Button.Group size="small" />);
it('renders correctly', () => {
const wrapper = render(<Button>Follow</Button>);
@ -45,6 +50,14 @@ describe('Button', () => {
// should insert space while loading
const wrapper5 = render(<Button loading>按钮</Button>);
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', () => {
@ -67,6 +80,22 @@ describe('Button', () => {
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', () => {
const wrapper = mount(<Button>Button Text</Button>);
// eslint-disable-next-line
@ -122,6 +151,17 @@ describe('Button', () => {
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', () => {
const wrapper = mount(
<Button target="_blank" href="http://ant.design">
@ -169,4 +209,27 @@ describe('Button', () => {
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();
});
});

22
components/button/button.tsx

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

56
components/config-provider/__tests__/locale.test.js

@ -2,10 +2,16 @@ import React from 'react';
import { mount } from 'enzyme';
import ConfigProvider from '..';
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 Modal from '../../modal';
describe('ConfigProvider.Locale', () => {
function $$(className) {
return document.body.querySelectorAll(className);
}
it('not throw', () => {
if (process.env.REACT === '15') {
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', () => {
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', () => {
const wrapper = mount(
<LocaleProvider locale={locale}>
<LocaleProvider locale={zhCN}>
<TimePicker />
</LocaleProvider>,
);
@ -36,7 +82,7 @@ describe('ConfigProvider.Locale', () => {
it('LocaleProvider > ConfigProvider', () => {
const wrapper = mount(
<LocaleProvider locale={locale}>
<LocaleProvider locale={zhCN}>
<ConfigProvider>
<TimePicker />
</ConfigProvider>
@ -48,7 +94,7 @@ describe('ConfigProvider.Locale', () => {
it('ConfigProvider > ConfigProvider', () => {
const wrapper = mount(
<ConfigProvider locale={locale}>
<ConfigProvider locale={zhCN}>
<ConfigProvider>
<TimePicker />
</ConfigProvider>

2
components/descriptions/index.zh-CN.md

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

22
components/form/__tests__/__snapshots__/demo.test.js.snap

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

27
components/form/demo/validate-other.md

@ -171,17 +171,15 @@ const Demo = () => {
</Form.Item>
<Form.Item label="Dragger">
<div className="dropbox">
<Form.Item name="dragger" valuePropName="fileList" getValueFromEvent={normFile} noStyle>
<Upload.Dragger name="files" action="/upload.do">
<p className="ant-upload-drag-icon">
<Inbox />
</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>
</Upload.Dragger>
</Form.Item>
</div>
<Form.Item name="dragger" valuePropName="fileList" getValueFromEvent={normFile} noStyle>
<Upload.Dragger name="files" action="/upload.do">
<p className="ant-upload-drag-icon">
<Inbox />
</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>
</Upload.Dragger>
</Form.Item>
</Form.Item>
<Form.Item wrapperCol={{ span: 12, offset: 6 }}>
@ -195,10 +193,3 @@ const Demo = () => {
ReactDOM.render(<Demo />, mountNode);
```
```css
#components-form-demo-validate-other .dropbox {
height: 180px;
line-height: 1.5;
}
```

2
components/form/index.en-US.md

@ -19,7 +19,7 @@ High performance Form component with data scope management. Including data colle
| Property | Description | Type | Default |
| --- | --- | --- | --- |
| 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)\[] | - |
| 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 |

2
components/form/index.zh-CN.md

@ -20,7 +20,7 @@ title: 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)\[] | - |
| form | 经 `Form.useForm()` 创建的 form 控制实例,不提供时会自动创建 | [FormInstance](#FormInstance) | - |
| hideRequiredMark | 隐藏所有表单项的必选标记 | boolean | false |

2
components/input-number/style/index.less

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

16
components/input/Search.tsx

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

15
components/input/__tests__/Search.test.js

@ -137,4 +137,19 @@ describe('Input.Search', () => {
expect(wrapper.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();
});
});

1
components/input/__tests__/__snapshots__/index.test.js.snap

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

2
components/locale-provider/index.tsx

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

2
components/page-header/index.en-US.md

@ -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 |
| footer | PageHeader's footer, generally used to render TabBar | ReactNode | - | 3.14.0 |
| onBack | back icon click event | `()=>void` | `()=>history.back()` | 3.14.0 |
> breadcrumbs will automatically disappear when configuring back icon.

2
components/page-header/index.zh-CN.md

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

1
components/select/index.en-US.md

@ -33,6 +33,7 @@ Select component to select value from options.
| dropdownMatchSelectWidth | Whether dropdown's width is same with select. | boolean | true | |
| dropdownRender | Customize dropdown content | (menuNode: ReactNode, props) => ReactNode | - | 3.11.0 |
| 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 | |
| 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 | |

1
components/select/index.zh-CN.md

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

2
components/table/index.zh-CN.md

@ -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 | - | |
| sortOrder | 排序的受控属性,外界可用此控制列的排序,可设置为 `'ascend'` `'descend'` `false` | boolean\|string | - | |
| 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 | - | |
| onCell | 设置单元格属性 | Function(record, rowIndex) | - | |
| onFilter | 本地模式下,确定筛选的运行函数 | Function | - | |

6
components/transfer/index.en-US.md

@ -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)\[] | \[] | |
| 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 | | |
| 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 }` | |
| 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 |
| 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 |
| 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\[] | \[] | |
| 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 |
| 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\[] | \[] | |
| 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 | | |
| 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 |

6
components/transfer/index.zh-CN.md

@ -27,18 +27,18 @@ title: Transfer
| dataSource | 数据源,其中的数据将会被渲染到左边一栏中,`targetKeys` 中指定的除外。 | [TransferItem](https://git.io/vMM64)\[] | \[] | |
| disabled | 是否禁用 | boolean | false | 3.10.0 |
| 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 }` | |
| listStyle | 两个穿梭框的自定义样式 | object | | |
| locale | 各种语言 | { itemUnit: string; itemsUnit: string; searchPlaceholder: string; notFoundContent: ReactNode; } | `{ itemUnit: '项', itemsUnit: '项', searchPlaceholder: '请输入搜索内容' }` | 3.9.0 |
| operations | 操作文案集合,顺序从上至下 | string\[] | \['>', '<'] | |
| render | 每行数据渲染函数,该函数的入参为 `dataSource` 中的项,返回值为 ReactElement。或者返回一个普通对象,其中 `label` 字段为 ReactElement,`value` 字段为 title | Function(record) | | |
| render | 每行数据渲染函数,该函数的入参为 `dataSource` 中的项,返回值为 ReactElement。或者返回一个普通对象,其中 `label` 字段为 ReactElement,`value` 字段为 title | (record) => ReactNode | | |
| selectedKeys | 设置哪些项应该被选中 | string\[] | \[] | |
| showSearch | 是否显示搜索框 | boolean | false | |
| showSelectAll | 是否展示全选勾选框 | boolean | true | 3.18.0 |
| style | 容器的自定义样式 | object | | 3.6.0 |
| targetKeys | 显示在右侧框数据的 key 集合 | string\[] | \[] | |
| titles | 标题集合,顺序从左至右 | string\[] | \['', ''] | |
| titles | 标题集合,顺序从左至右 | ReactNode\[] | \['', ''] | |
| onChange | 选项在两栏之间转移时的回调函数 | (targetKeys, direction, moveKeys): void | | |
| onScroll | 选项列表滚动时的回调函数 | (direction, event): void | | |
| onSearch | 搜索框内容时改变时的回调函数 | (direction: 'left'\|'right', value: string): void | - | 3.11.0 |

2
components/tree/Tree.tsx

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

1
components/tree/index.en-US.md

@ -33,6 +33,7 @@ Almost anything can be represented in a tree structure. Examples include directo
| loadData | Load data asynchronously | function(node) | - | |
| loadedKeys | (Controlled) Set loaded tree nodes. Need work with `loadData` | string\[] | \[] | 3.7.0 |
| multiple | Allows selecting multiple treeNodes | boolean | false | |
| selectable | whether can be selected | boolean | true | |
| 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 | |
| switcherIcon | customize collapse/expand icon of tree node | React.ReactElement | - | 3.12.0 |

1
components/tree/index.zh-CN.md

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

2
docs/spec/overview.en-US.md

@ -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.
- [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.

149
package.json

@ -1,46 +1,91 @@
{
"name": "antd",
"version": "4.0.0-alpha.3",
"title": "Ant Design",
"description": "An enterprise-class UI design language and React components implementation",
"homepage": "http://ant.design/",
"keywords": [
"ant",
"design",
"react",
"react-component",
"component",
"components",
"ui",
"design",
"framework",
"frontend"
],
"contributors": [
"ant"
"frontend",
"react",
"react-component",
"ui"
],
"publishConfig": {
"registry": "https://registry.npmjs.org/"
"homepage": "http://ant.design/",
"bugs": {
"url": "https://github.com/ant-design/ant-design/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/ant-design/ant-design"
},
"bugs": {
"url": "https://github.com/ant-design/ant-design/issues"
},
"main": "lib/index.js",
"module": "es/index.js",
"license": "MIT",
"contributors": [
"ant"
],
"files": [
"dist",
"lib",
"es"
],
"sideEffects": [
"dist/*",
"es/**/style/*",
"lib/**/style/*",
"*.less"
],
"main": "lib/index.js",
"module": "es/index.js",
"typings": "lib/index.d.ts",
"license": "MIT",
"peerDependencies": {
"react": ">=16.0.0",
"react-dom": ">=16.0.0"
"scripts": {
"api-collection": "antd-tools run api-collection",
"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",
"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": {
"@ant-design/create-react-context": "^0.2.4",
"@ant-design/icons": "^4.0.0-alpha.7",
@ -115,7 +160,7 @@
"antd-theme-generator": "^1.1.6",
"babel-eslint": "^10.0.1",
"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-description": "^0.1.4",
"bisheng-plugin-react": "^1.1.0",
@ -141,7 +186,6 @@
"eslint-plugin-react": "^7.14.2",
"eslint-tinker": "^0.5.0",
"fetch-jsonp": "^1.1.3",
"fs-extra": "^8.1.0",
"full-icu": "^1.3.0",
"glob": "^7.1.4",
"husky": "^3.0.2",
@ -168,7 +212,6 @@
"react-copy-to-clipboard": "^5.0.1",
"react-dnd": "^9.0.0",
"react-dnd-html5-backend": "^9.0.0",
"react-document-title": "^2.0.3",
"react-dom": "^16.9.0",
"react-github-button": "^0.1.11",
"react-helmet": "^6.0.0-beta",
@ -196,58 +239,13 @@
"xhr2": "^0.2.0",
"yaml-front-matter": "^4.0.0"
},
"scripts": {
"test": "jest --config .jest.js --no-cache",
"test-node": "jest --config .jest.node.js --no-cache",
"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"
"peerDependencies": {
"react": ">=16.0.0",
"react-dom": ">=16.0.0"
},
"husky": {
"hooks": {
"pre-commit": "pretty-quick --staged"
}
"publishConfig": {
"registry": "https://registry.npmjs.org/"
},
"sideEffects": [
"dist/*",
"es/**/style/*",
"lib/**/style/*",
"*.less"
],
"browserslist": [
"last 2 version",
"Firefox ESR",
"> 1%",
"ie >= 9"
],
"bundlesize": [
{
"path": "./dist/antd.min.js",
@ -257,5 +255,6 @@
"path": "./dist/antd.min.css",
"maxSize": "60 kB"
}
]
],
"title": "Ant Design"
}

2
scripts/check-demo.js

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

2
site/theme/en-US.js

@ -31,7 +31,7 @@ module.exports = {
'app.home.design-language': 'Design Language',
'app.home.solution': 'Solution',
'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-mobile-slogan':
"antd-mobile is the implementation of Ant Design's mobile specification",

9
site/theme/static/template.html

@ -1,14 +1,11 @@
<!DOCTYPE html>
<html>
<html {{ htmlAttributes | safe }}>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="description"
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>
<title>{% if title %}{{ title }}{% else %}{% endif %}</title>
{% if meta %}{{ meta | safe }}{% endif %}
<link
rel="icon"
href="https://gw.alipayobjects.com/zos/rmsportal/rlpTLlbMzTNYuZGGCVYM.png"

108
site/theme/template/Content/Article.jsx

@ -1,9 +1,10 @@
import React, { Children, cloneElement } from 'react';
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 { Timeline, Alert, Affix } from 'antd';
import EditButton from './EditButton';
import { getMetaDescription } from '../utils';
class Article extends React.Component {
shouldComponentUpdate(nextProps) {
@ -72,58 +73,61 @@ class Article extends React.Component {
const { meta, description } = content;
const { title, subtitle, filename } = meta;
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 (
<DocumentTitle title={`${title[locale] || title} - Ant Design`}>
{/* eslint-disable-next-line */}
<article className="markdown" onClick={this.onResourceClick}>
{isNotTranslated && (
<Alert
type="warning"
message={
<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>
</span>
}
/>
)}
<h1>
{title[locale] || title}
{!subtitle || locale === 'en-US' ? null : <span className="subtitle">{subtitle}</span>}
<EditButton
title={<FormattedMessage id="app.content.edit-page" />}
filename={filename}
/>
</h1>
{!description
? null
: utils.toReactComponent(
['section', { className: 'markdown' }].concat(getChildren(description)),
)}
{!content.toc || content.toc.length <= 1 || meta.toc === false ? null : (
<Affix className="toc-affix" offsetTop={16}>
{utils.toReactComponent(
['ul', { className: 'toc' }].concat(getChildren(content.toc)),
)}
</Affix>
)}
{this.getArticle(
utils.toReactComponent(
['section', { className: 'markdown' }].concat(getChildren(content.content)),
),
)}
{utils.toReactComponent(
[
'section',
{
className: 'markdown api-container',
},
].concat(getChildren(content.api || ['placeholder'])),
)}
</article>
</DocumentTitle>
/* eslint-disable-next-line */
<article className="markdown" onClick={this.onResourceClick}>
<Helmet>
{helmetTitle && <title>{helmetTitle}</title>}
{helmetTitle && <meta property="og:title" content={helmetTitle} />}
{metaDesc && <meta name="description" content={metaDesc} />}
</Helmet>
{isNotTranslated && (
<Alert
type="warning"
message={
<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>
</span>
}
/>
)}
<h1>
{title[locale] || title}
{!subtitle || locale === 'en-US' ? null : <span className="subtitle">{subtitle}</span>}
<EditButton title={<FormattedMessage id="app.content.edit-page" />} filename={filename} />
</h1>
{!description
? null
: utils.toReactComponent(
['section', { className: 'markdown' }].concat(getChildren(description)),
)}
{!content.toc || content.toc.length <= 1 || meta.toc === false ? null : (
<Affix className="toc-affix" offsetTop={16}>
{utils.toReactComponent(['ul', { className: 'toc' }].concat(getChildren(content.toc)))}
</Affix>
)}
{this.getArticle(
utils.toReactComponent(
['section', { className: 'markdown' }].concat(getChildren(content.content)),
),
)}
{utils.toReactComponent(
[
'section',
{
className: 'markdown api-container',
},
].concat(getChildren(content.api || ['placeholder'])),
)}
</article>
);
}
}

120
site/theme/template/Content/ComponentDoc.jsx

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

23
site/theme/template/Home/index.jsx

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

16
site/theme/template/Layout/index.jsx

@ -112,10 +112,26 @@ export default class Layout extends React.Component {
render() {
const { children, ...restProps } = this.props;
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 (
<>
<Helmet>
<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>
<IntlProvider locale={appLocale.locale} messages={appLocale.messages} defaultLocale="en-US">
<ConfigProvider locale={appLocale.locale === 'zh-CN' ? zhCN : null}>

27
site/theme/template/utils.jsx

@ -1,3 +1,6 @@
import flattenDeep from 'lodash/flattenDeep';
import flatten from 'lodash/flatten';
export function getMenuItems(moduleData, locale, categoryOrder, typeOrder) {
const menuMeta = moduleData.map(item => item.meta).filter(meta => !meta.skip);
@ -109,3 +112,27 @@ export function loadScript(src) {
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;
}

Loading…
Cancel
Save