Merge pull request #15154 from ant-design/feature

up to 3.14.0
This commit is contained in:
陈帅 2019-03-04 10:59:41 +08:00 committed by GitHub
commit 2996d8e625
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
110 changed files with 4715 additions and 451 deletions

View File

@ -14,6 +14,29 @@ timeline: true
* Major version release is not included in this schedule for breaking change and new features.
---
## 3.14.0
`2019-03-02`
- Two new components added this month:
- 🔥🔥🔥[Typography](https://github.com/ant-design/ant-design/pull/14250) provides basic formatting and common operations for text.
- 🔥🔥🔥[PageHeader](https://github.com/ant-design/ant-design/pull/13637) can be used to declare the page theme, display important information about the page that the user is interested in, and host the relevant page. Action item.
- 🌟 TimePicker provides `clearIcon` prop for customizing clear icon. [#14556](https://github.com/ant-design/ant-design/pull/14556)
- 🌟 Statistic.Countdown supports `onFinish` prop. [#14791](https://github.com/ant-design/ant-design/pull/14791)
- 🌟 Collapse.Panel support `extra` prop. [62e65d](https://github.com/ant-design/ant-design/commit/62e65d955065b1862240f9f30d84de44349a0cf9)
- DatePicker
- 🐞 Fix not support name prop. [#15029](https://github.com/ant-design/ant-design/pull/15029)
- 🌟 Supports `separator` prop. [#15055](https://github.com/ant-design/ant-design/pull/15055)
- 🌟 The Form supports `labelCol` & `wrapperCol` prop. [#15038](https://github.com/ant-design/ant-design/pull/15038)
- 🌟 Add type `more` for Icon. [#15047](https://github.com/ant-design/ant-design/pull/15047)
- 🐞 Fix Table filter can not support other type of value. [#15046](https://github.com/ant-design/ant-design/pull/15046)
- 🐞 Fix Spin `wrapperClassName` setting `padding` icon is not centered. [#15056](https://github.com/ant-design/ant-design/pull/15056)
- 🐞 Fix Calendar won't trigger `onPanelChange` correctly in some cases. [#15063](https://github.com/ant-design/ant-design/pull/15063)
- 🌟 Select supports `showArrow` in multi-select mode. [#15091](https://github.com/ant-design/ant-design/pull/15091)
- 🌟 Adjusted multiple TypeScript types
- 🐞 Fixed a problem with the `onPanelChange` TypeScript declaration missing. [#15043](https://github.com/ant-design/ant-design/pull/15043)
- 🐞 Fix the TypeScript type problem for Table `Column Filter`. [#15056](https://github.com/ant-design/ant-design/pull/15056)
- 🌟 Support goto button in Pagination. [#14819](https://github.com/ant-design/ant-design/pull/14819)
## 3.13.6

View File

@ -15,6 +15,30 @@ timeline: true
---
## 3.14.0
`2019-03-02`
- 本月新增了两个组件:
- 🔥🔥🔥 [Typography](https://github.com/ant-design/ant-design/pull/14250) 提供了文本的基本格式及常见操作。
- 🔥🔥🔥 [PageHeader](https://github.com/ant-design/ant-design/pull/13637) 可用于声明页面主题、展示用户所关注的页面重要信息,以及承载与当前页相关的操作项。
- 🌟 TimePicker 新增了 `clearIcon` prop用于自定义清除图标。[#14556](https://github.com/ant-design/ant-design/pull/14556)
- 🌟Statistic.Countdown 支持 `onFinish` prop。[#14791](https://github.com/ant-design/ant-design/pull/14791)
- 🌟 Collapse.Panel 新增了 `extra`。[62e65d](https://github.com/ant-design/ant-design/commit/62e65d955065b1862240f9f30d84de44349a0cf9)
- DatePicker
- 🐞 修复 `name` prop 无效的问题。[#15029](https://github.com/ant-design/ant-design/pull/15029)
- 🌟 支持 `separator` prop。[#15055](https://github.com/ant-design/ant-design/pull/15055)
- 🌟 Form 支持 `labelCol` & `wrapperCol` prop。[#15038](https://github.com/ant-design/ant-design/pull/15038)
- 🌟 Icon 增加了 `more` 的图标。[#15047](https://github.com/ant-design/ant-design/pull/15047)
- 🐞 修复 Table 筛选不支持 string 以外类型的问题。[#15046](https://github.com/ant-design/ant-design/pull/15046)
- 🐞 修复 Spin `wrapperClassName` 设置 `padding` 图标不居中的问题。[#15056](https://github.com/ant-design/ant-design/pull/15056)
- 🐞 修复 Calendar `onPanelChange` 在某些情况下不会触发的问题。[#15063](https://github.com/ant-design/ant-design/pull/15063)
- 🌟 Select 在多选模式下支持 `showArrow`。[#15091](https://github.com/ant-design/ant-design/pull/15091)
- 🐞 调整了多处 TypeScript 的类型
- 🐞 修复 `onPanelChange` TypeScript 声明缺失的问题。[#15043](https://github.com/ant-design/ant-design/pull/15043)
- 🐞 订正了 Table `column filter` 的 TypeScript 类型问题。[#14777](https://github.com/ant-design/ant-design/issues/14777)
- 🌟 Pagination 支持添加分页跳转按钮。 [#14819](https://github.com/ant-design/ant-design/pull/14819)
## 3.13.6
`2019-02-23`
@ -423,7 +447,7 @@ timeline: true
- 🐞 修复 RangePicker 在 `small` 模式不对齐的问题。[#13105](https://github.com/ant-design/ant-design/issues/13105)
- 🐞 修复 Dropdown 字体大小影响到头像的问题。[#13091](https://github.com/ant-design/ant-design/issues/13091)
- 🐞 修复 tabBarGutter 无法在垂直模式下工作的问题。[#12968](https://github.com/ant-design/ant-design/issues/12968)
- 🌟 调整了多处 typescript 的类型。
- 🌟 调整了多处 TypeScript 的类型。
## 3.10.7
@ -957,7 +981,7 @@ timeline: true
* 🌟 支持 Ant Design 站点的离线模式。[#10625](https://github.com/ant-design/ant-design/issues/10625)
* 🌟 `Transfer` 新增 `style` 以及 `operationStyle` 属性配置样式。[@eduludi](https://github.com/eduludi)
* 🌟 `Message` 增加 promise 化的回调接口。[#10421](https://github.com/ant-design/ant-design/issues/10421) [@zhujinxuan](https://github.com/zhujinxuan)
* 🐞 修复编译时 typescript v2.9.1兼容性问题。[#10729](https://github.com/ant-design/ant-design/issues/10729) [@karol-majewski](https://github.com/karol-majewski)
* 🐞 修复编译时 TypeScript v2.9.1兼容性问题。[#10729](https://github.com/ant-design/ant-design/issues/10729) [@karol-majewski](https://github.com/karol-majewski)
* 🐞 修复 `Menu` 嵌套超过两层时选中最里层后对应最外层没有亮起问题。[#8666](https://github.com/ant-design/ant-design/issues/8666) [@stonehank](https://github.com/stonehank)
* 🐞 修复 `Affix` 组件 offsetBottom 无效问题。[#10674](https://github.com/ant-design/ant-design/issues/10674)
@ -1039,13 +1063,13 @@ timeline: true
- Select
- 🐞 修复 `placeholder` 的 ts 类型问题。[#10282](https://github.com/ant-design/ant-design/pull/10282) [@thomasthiebaud](https://github.com/thomasthiebaud)
- 🐞 修复不显示箭头时多余的空白。[#10296](https://github.com/ant-design/ant-design/pull/10296)
- 🐞 修复属性 `value`typescript 类型错误。[#10336](https://github.com/ant-design/ant-design/pull/10336) [@paranoidjk](https://github.com/paranoidjk)
- 🐞 修复属性 `value`TypeScript 类型错误。[#10336](https://github.com/ant-design/ant-design/pull/10336) [@paranoidjk](https://github.com/paranoidjk)
- Input
- 🐞 修复 `Input.Search` 当 disabled 为 true 时按钮没有被禁用的问题。[#10040](https://github.com/ant-design/ant-design/issues/10040)
- 🐞 修复 `Input.Group` 在表单中对齐的问题。[#10281](https://github.com/ant-design/ant-design/issues/10281)
- Form
- 🐞 修复 `Form.onValuesChange` 的 ts 类型错误。[#10231](https://github.com/ant-design/ant-design/pull/10231) [@whtsky](https://github.com/whtsky)
- 🐞 修复 `ComponentDecorator` typescript 定义的错误。[#10324](https://github.com/ant-design/ant-design/pull/10324) [@paranoidjk](https://github.com/paranoidjk)
- 🐞 修复 `ComponentDecorator` TypeScript 定义的错误。[#10324](https://github.com/ant-design/ant-design/pull/10324) [@paranoidjk](https://github.com/paranoidjk)
- 🐞 修复 `Divider` 为 dashed 时的样式问题。[#10216](https://github.com/ant-design/ant-design/issues/10216)
- 🐞 修复 `Spin` 覆盖层的展示问题。[#10227](https://github.com/ant-design/ant-design/issues/10227)
- 🐞 修复 `Notification` 鼠标 hover 是图标的颜色问题。[#10272](https://github.com/ant-design/ant-design/issues/10272)

View File

@ -37,6 +37,7 @@ Array [
"Modal",
"Statistic",
"notification",
"PageHeader",
"Pagination",
"Popconfirm",
"Popover",
@ -59,6 +60,7 @@ Array [
"TimePicker",
"Timeline",
"Tooltip",
"Typography",
"Mention",
"Upload",
"version",

View File

@ -28,7 +28,9 @@ export default function wrapperRaf(callback: () => void, delayFrames: number = 1
return myId;
}
wrapperRaf.cancel = function(pid: number) {
wrapperRaf.cancel = function(pid?: number) {
if (pid === undefined) return;
raf.cancel(ids[pid]);
delete ids[pid];
};

View File

@ -0,0 +1,61 @@
import * as React from 'react';
import { findDOMNode } from 'react-dom';
import ResizeObserver from 'resize-observer-polyfill';
type DomElement = Element | null;
interface ResizeObserverProps {
children?: React.ReactNode;
disabled?: boolean;
onResize?: () => void;
}
class ReactResizeObserver extends React.Component<ResizeObserverProps, {}> {
resizeObserver: ResizeObserver | null = null;
componentDidMount() {
this.onComponentUpdated();
}
componentDidUpdate() {
this.onComponentUpdated();
}
componentWillUnmount() {
this.destroyObserver();
}
onComponentUpdated() {
const { disabled } = this.props;
const element = findDOMNode(this) as DomElement;
if (!this.resizeObserver && !disabled && element) {
// Add resize observer
this.resizeObserver = new ResizeObserver(this.onResize);
this.resizeObserver.observe(element);
} else if (disabled) {
// Remove resize observer
this.destroyObserver();
}
}
onResize = () => {
const { onResize } = this.props;
if (onResize) {
onResize();
}
};
destroyObserver() {
if (this.resizeObserver) {
this.resizeObserver.disconnect();
this.resizeObserver = null;
}
}
render() {
const { children } = this.props;
return children;
}
}
export default ReactResizeObserver;

View File

@ -0,0 +1,68 @@
/**
* Wrap of sub component which need use as Button capacity (like Icon component).
* This helps accessibility reader to tread as a interactive button to operation.
*/
import * as React from 'react';
import KeyCode from 'rc-util/lib/KeyCode';
interface TransButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
onClick?: () => void;
}
const inlineStyle: React.CSSProperties = {
border: 0,
background: 'transparent',
padding: 0,
lineHeight: 'inherit',
};
class TransButton extends React.Component<TransButtonProps> {
button?: HTMLButtonElement;
lastKeyCode?: number;
onKeyDown: React.KeyboardEventHandler<HTMLButtonElement> = event => {
const { keyCode } = event;
if (keyCode === KeyCode.ENTER) {
event.preventDefault();
}
};
onKeyUp: React.KeyboardEventHandler<HTMLButtonElement> = event => {
const { keyCode } = event;
const { onClick } = this.props;
if (keyCode === KeyCode.ENTER && onClick) {
onClick();
}
};
setRef = (btn: HTMLButtonElement) => {
this.button = btn;
};
focus() {
if (this.button) {
this.button.focus();
}
}
blur() {
if (this.button) {
this.button.blur();
}
}
render() {
const { style } = this.props;
return (
<button
ref={this.setRef}
{...this.props}
onKeyDown={this.onKeyDown}
onKeyUp={this.onKeyUp}
style={{ ...inlineStyle, ...style }}
/>
);
}
}
export default TransButton;

View File

@ -1,3 +1,5 @@
export type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
// https://stackoverflow.com/questions/46176165/ways-to-get-string-literal-type-of-array-values-without-enum-overhead
export const tuple = <T extends string[]>(...args: T) => args;
export const tupleNum = <T extends number[]>(...args: T) => args;

View File

@ -435,7 +435,10 @@ exports[`renders ./components/badge/demo/change.md correctly 1`] = `
width="1em"
>
<path
d="M848 474H550V152h-76v322H176c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h298v322h76V550h298c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z"
d="M482 152h60q8 0 8 8v704q0 8-8 8h-60q-8 0-8-8V160q0-8 8-8z"
/>
<path
d="M176 474h672q8 0 8 8v60q0 8-8 8H176q-8 0-8-8v-60q0-8 8-8z"
/>
</svg>
</i>

View File

@ -28,6 +28,7 @@ interface PanelProps {
showArrow?: boolean;
forceRender?: boolean;
disabled?: boolean;
extra?: React.ReactNode;
}
export default class Collapse extends React.Component<CollapseProps, any> {

View File

@ -13,6 +13,7 @@ export interface CollapsePanelProps {
prefixCls?: string;
forceRender?: boolean;
id?: string;
extra?: React.ReactNode;
}
export default class CollapsePanel extends React.Component<CollapsePanelProps, {}> {

View File

@ -1,5 +1,90 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Collapse should render extra node of panel 1`] = `
<div
class="ant-collapse"
>
<div
class="ant-collapse-item"
>
<div
aria-expanded="false"
class="ant-collapse-header"
role="button"
tabindex="0"
>
<i
aria-label="icon: right"
class="anticon anticon-right ant-collapse-arrow"
>
<svg
aria-hidden="true"
class=""
data-icon="right"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M765.7 486.8L314.9 134.7A7.97 7.97 0 0 0 302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 0 0 0-50.4z"
/>
</svg>
</i>
header
<div
class="ant-collapse-extra"
>
<button
type="button"
>
action
</button>
</div>
</div>
</div>
<div
class="ant-collapse-item"
>
<div
aria-expanded="false"
class="ant-collapse-header"
role="button"
tabindex="0"
>
<i
aria-label="icon: right"
class="anticon anticon-right ant-collapse-arrow"
>
<svg
aria-hidden="true"
class=""
data-icon="right"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M765.7 486.8L314.9 134.7A7.97 7.97 0 0 0 302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 0 0 0-50.4z"
/>
</svg>
</i>
header
<div
class="ant-collapse-extra"
>
<button
type="button"
>
action
</button>
</div>
</div>
</div>
</div>
`;
exports[`Collapse should support remove expandIcon 1`] = `
<div
class="ant-collapse"

View File

@ -11,4 +11,14 @@ describe('Collapse', () => {
);
expect(wrapper.render()).toMatchSnapshot();
});
it('should render extra node of panel', () => {
const wrapper = mount(
<Collapse>
<Collapse.Panel header="header" extra={<button type="button">action</button>} />
<Collapse.Panel header="header" extra={<button type="button">action</button>} />
</Collapse>
);
expect(wrapper.render()).toMatchSnapshot();
});
});

View File

@ -35,6 +35,7 @@ A content area which can be collapsed and expanded.
| header | Title of the panel | string\|ReactNode | - |
| key | Unique key identifying the panel from among its siblings | string | - |
| showArrow | If `false`, panel will not show arrow icon | boolean | `true` |
| extra | extra element in the corner | ReactNode | - |
## FAQ

View File

@ -36,6 +36,7 @@ cols: 1
| header | 面板头内容 | string\|ReactNode | 无 |
| key | 对应 activeKey | string | 无 |
| showArrow | 是否展示当前面板上的箭头 | boolean | `true` |
| extra | 自定义渲染每个面板右上角的内容 | ReactNode | - |
## FAQ

View File

@ -7528,87 +7528,87 @@ exports[`ConfigProvider components InputNumber prefixCls 1`] = `
`;
exports[`ConfigProvider components Layout configProvider 1`] = `
<div
<section
class="config-layout"
>
<div
<aside
class="config-layout-sider config-layout-sider-dark"
style="flex:0 0 200px;max-width:200px;min-width:200px;width:200px"
>
<div
class="config-layout-sider-children"
/>
</div>
<div
</aside>
<section
class="config-layout"
>
<div
<header
class="config-layout-header"
/>
<div
<main
class="config-layout-content"
/>
<div
<footer
class="config-layout-footer"
/>
</div>
</div>
</section>
</section>
`;
exports[`ConfigProvider components Layout normal 1`] = `
<div
<section
class="ant-layout"
>
<div
<aside
class="ant-layout-sider ant-layout-sider-dark"
style="flex:0 0 200px;max-width:200px;min-width:200px;width:200px"
>
<div
class="ant-layout-sider-children"
/>
</div>
<div
</aside>
<section
class="ant-layout"
>
<div
<header
class="ant-layout-header"
/>
<div
<main
class="ant-layout-content"
/>
<div
<footer
class="ant-layout-footer"
/>
</div>
</div>
</section>
</section>
`;
exports[`ConfigProvider components Layout prefixCls 1`] = `
<div
<section
class="prefix-Layout"
>
<div
<aside
class="prefix-sider prefix-sider-dark"
style="flex:0 0 200px;max-width:200px;min-width:200px;width:200px"
>
<div
class="prefix-sider-children"
/>
</div>
<div
</aside>
<section
class="prefix-Layout"
>
<div
<header
class="prefix-header"
/>
<div
<main
class="prefix-content"
/>
<div
<footer
class="prefix-footer"
/>
</div>
</div>
</section>
</section>
`;
exports[`ConfigProvider components List configProvider 1`] = `

View File

@ -18,6 +18,15 @@ export interface ConfigConsumerProps {
autoInsertSpaceInButton?: boolean;
}
export const configConsumerProps = [
'getPopupContainer',
'rootPrefixCls',
'getPrefixCls',
'renderEmpty',
'csp',
'autoInsertSpaceInButton',
];
interface ConfigProviderProps {
getPopupContainer?: (triggerNode?: HTMLElement) => HTMLElement;
prefixCls?: string;
@ -89,10 +98,14 @@ interface ConsumerConfig {
prefixCls: string;
}
interface ConstructorProps {
displayName?: string;
}
export function withConfigConsumer<ExportProps extends BasicExportProps>(config: ConsumerConfig) {
return function <ComponentDef>(Component: IReactComponent): React.SFC<ExportProps> & ComponentDef {
return function<ComponentDef>(Component: IReactComponent): React.SFC<ExportProps> & ComponentDef {
// Wrap with ConfigConsumer. Since we need compatible with react 15, be care when using ref methods
return ((props: ExportProps) => (
const SFC = ((props: ExportProps) => (
<ConfigConsumer>
{(configProps: ConfigConsumerProps) => {
const { prefixCls: basicPrefixCls } = config;
@ -103,6 +116,13 @@ export function withConfigConsumer<ExportProps extends BasicExportProps>(config:
}}
</ConfigConsumer>
)) as React.SFC<ExportProps> & ComponentDef;
const cons: ConstructorProps = Component.constructor as ConstructorProps;
const name = (cons && cons.displayName) || Component.name || 'Component';
SFC.displayName = `withConfigConsumer(${name})`;
return SFC;
};
}

View File

@ -70,6 +70,7 @@ class RangePicker extends React.Component<any, RangePickerState> {
static defaultProps = {
allowClear: true,
showToday: false,
separator: '~',
};
static getDerivedStateFromProps(nextProps: any, prevState: any) {
@ -275,6 +276,7 @@ class RangePicker extends React.Component<any, RangePickerState> {
dateRender,
onCalendarChange,
suffixIcon,
separator,
} = props;
const prefixCls = getPrefixCls('calendar', customizePrefixCls);
@ -323,6 +325,7 @@ class RangePicker extends React.Component<any, RangePickerState> {
const calendar = (
<RangeCalendar
{...calendarProps}
seperator={separator}
onChange={onCalendarChange}
format={format}
prefixCls={prefixCls}
@ -385,7 +388,7 @@ class RangePicker extends React.Component<any, RangePickerState> {
className={`${prefixCls}-range-picker-input`}
tabIndex={-1}
/>
<span className={`${prefixCls}-range-picker-separator`}> ~ </span>
<span className={`${prefixCls}-range-picker-separator`}> {separator} </span>
<input
disabled={props.disabled}
readOnly

View File

@ -314,4 +314,9 @@ describe('RangePicker', () => {
wrapper.find('.ant-calendar-range-quick-selector Tag').simulate('click');
expect(handleOpenChange).toBeCalledWith(false);
});
it('customize separator', () => {
const wrapper = mount(<RangePicker separator="test" />);
expect(wrapper.render()).toMatchSnapshot();
});
});

View File

@ -1,5 +1,54 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`RangePicker customize separator 1`] = `
<span
class="ant-calendar-picker"
tabindex="0"
>
<span
class="ant-calendar-picker-input ant-input"
>
<input
class="ant-calendar-range-picker-input"
placeholder="Start date"
readonly=""
tabindex="-1"
value=""
/>
<span
class="ant-calendar-range-picker-separator"
>
test
</span>
<input
class="ant-calendar-range-picker-input"
placeholder="End date"
readonly=""
tabindex="-1"
value=""
/>
<i
aria-label="icon: calendar"
class="anticon anticon-calendar ant-calendar-picker-icon"
>
<svg
aria-hidden="true"
class=""
data-icon="calendar"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M880 184H712v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H384v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H144c-17.7 0-32 14.3-32 32v664c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V216c0-17.7-14.3-32-32-32zm-40 656H184V460h656v380zM184 392V256h128v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h256v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h128v136H184z"
/>
</svg>
</i>
</span>
</span>
`;
exports[`RangePicker show month panel according to value 1`] = `
<div>
<div

View File

@ -114,20 +114,21 @@ The following APIs are shared by DatePicker, MonthPicker, RangePicker, WeekPicke
### RangePicker
| Property | Description | Type | Default |
| -------- | ----------- | ---- | ------- |
| defaultValue | to set default date | \[[moment](http://momentjs.com/), [moment](http://momentjs.com/)] | - |
| defaultPickerValue | to set default picker date | \[[moment](http://momentjs.com/), [moment](http://momentjs.com/)\] | - |
| disabledTime | to specify the time that cannot be selected | function(dates: \[moment, moment], partial: `'start'|'end'`) | - |
| format | to set the date format, refer to [moment.js](http://momentjs.com/). When an array is provided, all values are used for parsing and first value is used for formatting. | string \| string[] | "YYYY-MM-DD HH:mm:ss" |
| ranges | preseted ranges for quick selection | { \[range: string]: [moment](http://momentjs.com/)\[] } \| { \[range: string]: () => [moment](http://momentjs.com/)\[] } | - |
| renderExtraFooter | render extra footer in panel | () => React.ReactNode | - |
| showTime | to provide an additional time selection | object\|boolean | [TimePicker Options](/components/time-picker/#API) |
| showTime.defaultValue | to set default time of selected date, [demo](#components-date-picker-demo-disabled-date) | [moment](http://momentjs.com/)\[] | \[moment(), moment()] |
| value | to set date | \[[moment](http://momentjs.com/), [moment](http://momentjs.com/)] | - |
| onCalendarChange | a callback function, can be executed when the start time or the end time of the range is changing | function(dates: \[moment, moment], dateStrings: \[string, string]) | - |
| onChange | a callback function, can be executed when the selected time is changing | function(dates: \[moment, moment], dateStrings: \[string, string]) | - |
| onOk | callback when click ok button | function(dates: [moment](http://momentjs.com/)\[]) | - |
| Property | Description | Type | Default | Version |
| -------- | ----------- | ---- | ------- | --------------- |
| defaultValue | to set default date | \[[moment](http://momentjs.com/), [moment](http://momentjs.com/)] | - | |
| defaultPickerValue | to set default picker date | \[[moment](http://momentjs.com/), [moment](http://momentjs.com/)\] | - | |
| disabledTime | to specify the time that cannot be selected | function(dates: \[moment, moment], partial: `'start'|'end'`) | - | |
| format | to set the date format, refer to [moment.js](http://momentjs.com/). When an array is provided, all values are used for parsing and first value is used for formatting. | string \| string[] | "YYYY-MM-DD HH:mm:ss" | |
| ranges | preseted ranges for quick selection | { \[range: string]: [moment](http://momentjs.com/)\[] } \| { \[range: string]: () => [moment](http://momentjs.com/)\[] } | - | |
| renderExtraFooter | render extra footer in panel | () => React.ReactNode | - | |
| separator | set separator between inputs | string | '~' | 3.14.0 |
| showTime | to provide an additional time selection | object\|boolean | [TimePicker Options](/components/time-picker/#API) | |
| showTime.defaultValue | to set default time of selected date, [demo](#components-date-picker-demo-disabled-date) | [moment](http://momentjs.com/)\[] | \[moment(), moment()] | |
| value | to set date | \[[moment](http://momentjs.com/), [moment](http://momentjs.com/)] | - | |
| onCalendarChange | a callback function, can be executed when the start time or the end time of the range is changing | function(dates: \[moment, moment], dateStrings: \[string, string]) | - | |
| onChange | a callback function, can be executed when the selected time is changing | function(dates: \[moment, moment], dateStrings: \[string, string]) | - | |
| onOk | callback when click ok button | function(dates: [moment](http://momentjs.com/)\[]) | - | |
<style>
.code-box-demo .ant-calendar-picker {

View File

@ -117,20 +117,21 @@ moment.locale('zh-cn');
### RangePicker
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| defaultValue | 默认日期 | [moment](http://momentjs.com/)\[] | 无 |
| defaultPickerValue | 默认面板日期 | [moment](http://momentjs.com/)\[] | 无 |
| disabledTime | 不可选择的时间 | function(dates: \[moment, moment\], partial: `'start'|'end'`) | 无 |
| format | 展示的日期格式 | string | "YYYY-MM-DD HH:mm:ss" |
| ranges       | 预设时间范围快捷选择 | { \[range: string]: [moment](http://momentjs.com/)\[] } \| { \[range: string]: () => [moment](http://momentjs.com/)\[] } | 无 |
| renderExtraFooter | 在面板中添加额外的页脚 | () => React.ReactNode | - |
| showTime | 增加时间选择功能 | Object\|boolean | [TimePicker Options](/components/time-picker/#API) |
| showTime.defaultValue | 设置用户选择日期时默认的时分秒,[例子](#components-date-picker-demo-disabled-date) | [moment](http://momentjs.com/)\[] | \[moment(), moment()] |
| value | 日期 | [moment](http://momentjs.com/)\[] | 无 |
| onCalendarChange | 待选日期发生变化的回调 | function(dates: \[moment, moment\], dateStrings: \[string, string\]) | 无 |
| onChange | 日期范围发生变化的回调 | function(dates: \[moment, moment\], dateStrings: \[string, string\]) | 无 |
| onOk | 点击确定按钮的回调 | function(dates: [moment](http://momentjs.com/)\[]) | - |
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| defaultValue | 默认日期 | [moment](http://momentjs.com/)\[] | 无 | |
| defaultPickerValue | 默认面板日期 | [moment](http://momentjs.com/)\[] | 无 | |
| disabledTime | 不可选择的时间 | function(dates: \[moment, moment\], partial: `'start'|'end'`) | 无 | |
| format | 展示的日期格式 | string | "YYYY-MM-DD HH:mm:ss" | |
| ranges | 预设时间范围快捷选择 | { \[range: string]: [moment](http://momentjs.com/)\[] } \| { \[range: string]: () => [moment](http://momentjs.com/)\[] } | 无 | |
| renderExtraFooter | 在面板中添加额外的页脚 | () => React.ReactNode | - | |
| separator | 设置分隔符 | string | '~' | 3.14.0 |
| showTime | 增加时间选择功能 | Object\|boolean | [TimePicker Options](/components/time-picker/#API) | |
| showTime.defaultValue | 设置用户选择日期时默认的时分秒,[例子](#components-date-picker-demo-disabled-date) | [moment](http://momentjs.com/)\[] | \[moment(), moment()] | |
| value | 日期 | [moment](http://momentjs.com/)\[] | 无 | |
| onCalendarChange | 待选日期发生变化的回调 | function(dates: \[moment, moment\], dateStrings: \[string, string\]) | 无 | |
| onChange | 日期范围发生变化的回调 | function(dates: \[moment, moment\], dateStrings: \[string, string\]) | 无 | |
| onOk | 点击确定按钮的回调 | function(dates: [moment](http://momentjs.com/)\[]) | - | |
<style>
.code-box-demo .ant-calendar-picker {

View File

@ -16,9 +16,11 @@
.@{calendar-prefix-cls}-range-picker-separator {
display: inline-block;
width: 10px;
min-width: 10px;
height: 100%;
color: @text-color-secondary;
white-space: nowrap;
text-align: center;
vertical-align: top;
}
@ -61,20 +63,23 @@
&-middle {
position: absolute;
left: 50%;
width: 20px;
z-index: 1;
height: @input-box-height;
margin-left: -132px;
margin: 1px 0 0 0;
padding: 0 200px 0 0;
color: @text-color-secondary;
line-height: @input-box-height;
text-align: center;
transform: translateX(-50%);
}
&-right .@{calendar-prefix-cls}-date-input-wrap {
margin-left: -118px;
margin-left: -90px;
}
&.@{calendar-prefix-cls}-time &-middle {
margin-left: -12px;
padding: 0 10px 0 0;
transform: translateX(-50%);
}
&.@{calendar-prefix-cls}-time &-right .@{calendar-prefix-cls}-date-input-wrap {

View File

@ -33,7 +33,10 @@ exports[`renders ./components/drawer/demo/form-in-drawer.md correctly 1`] = `
width="1em"
>
<path
d="M848 474H550V152h-76v322H176c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h298v322h76V550h298c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z"
d="M482 152h60q8 0 8 8v704q0 8-8 8h-60q-8 0-8-8V160q0-8 8-8z"
/>
<path
d="M176 474h672q8 0 8 8v60q0 8-8 8H176q-8 0-8-8v-60q0-8 8-8z"
/>
</svg>
</i>

View File

@ -45,8 +45,7 @@
}
// Patch for popup adjust
.@{ant-prefix}-select-dropdown--multiple .@{ant-prefix}-select-dropdown-menu-item {
.@{empty-prefix-cls}-small {
margin-left: @control-padding-horizontal + 20;
}
.@{ant-prefix}-select-dropdown--empty .@{ant-prefix}-select-dropdown-menu-item {
padding-right: 0;
padding-left: 0;
}

View File

@ -4,11 +4,13 @@ import classNames from 'classnames';
import createDOMForm from 'rc-form/lib/createDOMForm';
import createFormField from 'rc-form/lib/createFormField';
import omit from 'omit.js';
import FormItem from './FormItem';
import { FIELD_META_PROP, FIELD_DATA_PROP } from './constants';
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
import { ColProps } from '../grid/col';
import { Omit, tuple } from '../_util/type';
import warning from '../_util/warning';
import FormItem from './FormItem';
import { FIELD_META_PROP, FIELD_DATA_PROP } from './constants';
import { FormContext } from './context';
type FormCreateOptionMessagesCallback = (...args: any[]) => string;
@ -36,6 +38,8 @@ export interface FormProps extends React.FormHTMLAttributes<HTMLFormElement> {
className?: string;
prefixCls?: string;
hideRequiredMark?: boolean;
labelCol?: ColProps;
wrapperCol?: ColProps;
}
export type ValidationRule = {
@ -204,10 +208,6 @@ export default class Form extends React.Component<FormProps, any> {
hideRequiredMark: PropTypes.bool,
};
static childContextTypes = {
vertical: PropTypes.bool,
};
static Item = FormItem;
static createFormField = createFormField;
@ -229,13 +229,6 @@ export default class Form extends React.Component<FormProps, any> {
warning(!props.form, 'Form', 'It is unnecessary to pass `form` to `Form` after antd@1.7.0.');
}
getChildContext() {
const { layout } = this.props;
return {
vertical: layout === 'vertical',
};
}
renderForm = ({ getPrefixCls }: ConfigConsumerProps) => {
const { prefixCls: customizePrefixCls, hideRequiredMark, className = '', layout } = this.props;
const prefixCls = getPrefixCls('form', customizePrefixCls);
@ -262,6 +255,11 @@ export default class Form extends React.Component<FormProps, any> {
};
render() {
return <ConfigConsumer>{this.renderForm}</ConfigConsumer>;
const { wrapperCol, labelCol, layout } = this.props;
return (
<FormContext.Provider value={{ wrapperCol, labelCol, vertical: layout === 'vertical' }}>
<ConfigConsumer>{this.renderForm}</ConfigConsumer>
</FormContext.Provider>
);
}
}

View File

@ -3,13 +3,14 @@ import * as ReactDOM from 'react-dom';
import * as PropTypes from 'prop-types';
import classNames from 'classnames';
import Animate from 'rc-animate';
import { FIELD_META_PROP, FIELD_DATA_PROP } from './constants';
import Row from '../grid/row';
import Col, { ColProps } from '../grid/col';
import Icon from '../icon';
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
import warning from '../_util/warning';
import { tuple } from '../_util/type';
import { FIELD_META_PROP, FIELD_DATA_PROP } from './constants';
import { FormContext, FormContextProps } from './context';
const ValidateStatuses = tuple('success', 'warning', 'error', 'validating', '');
@ -33,10 +34,6 @@ function intersperseSpace<T>(list: Array<T>): Array<T | string> {
return list.reduce((current, item) => [...current, ' ', item], []).slice(1);
}
export interface FormItemContext {
vertical: boolean;
}
export default class FormItem extends React.Component<FormItemProps, any> {
static defaultProps = {
hasFeedback: false,
@ -57,12 +54,6 @@ export default class FormItem extends React.Component<FormItemProps, any> {
colon: PropTypes.bool,
};
static contextTypes = {
vertical: PropTypes.bool,
};
context: FormItemContext;
helpShow = false;
componentDidMount() {
@ -267,15 +258,28 @@ export default class FormItem extends React.Component<FormItemProps, any> {
}
renderWrapper(prefixCls: string, children: React.ReactNode) {
return (
<FormContext.Consumer>
{({ wrapperCol: contextWrapperCol, vertical }: FormContextProps) => {
const { wrapperCol } = this.props;
const mergedWrapperCol: ColProps =
('wrapperCol' in this.props ? wrapperCol : contextWrapperCol) || {};
const className = classNames(
`${prefixCls}-item-control-wrapper`,
wrapperCol && wrapperCol.className,
mergedWrapperCol.className,
);
// No pass FormContext since it's useless
return (
<Col {...wrapperCol} className={className} key="wrapper">
<FormContext.Provider value={{ vertical }}>
<Col {...mergedWrapperCol} className={className} key="wrapper">
{children}
</Col>
</FormContext.Provider>
);
}}
</FormContext.Consumer>
);
}
@ -323,25 +327,30 @@ export default class FormItem extends React.Component<FormItemProps, any> {
};
renderLabel(prefixCls: string) {
return (
<FormContext.Consumer>
{({ vertical, labelCol: contextLabelCol }: FormContextProps) => {
const { label, labelCol, colon, id } = this.props;
const context = this.context;
const required = this.isRequired();
const labelColClassName = classNames(`${prefixCls}-item-label`, labelCol && labelCol.className);
const mergedLabelCol: ColProps =
('labelCol' in this.props ? labelCol : contextLabelCol) || {};
const labelColClassName = classNames(`${prefixCls}-item-label`, mergedLabelCol.className);
const labelClassName = classNames({
[`${prefixCls}-item-required`]: required,
});
let labelChildren = label;
// Keep label is original where there should have no colon
const haveColon = colon && !context.vertical;
const haveColon = colon && !vertical;
// Remove duplicated user input colon
if (haveColon && typeof label === 'string' && (label as string).trim() !== '') {
labelChildren = (label as string).replace(/[|:]\s*$/, '');
}
return label ? (
<Col {...labelCol} className={labelColClassName} key="label">
<Col {...mergedLabelCol} className={labelColClassName} key="label">
<label
htmlFor={id || this.getId()}
className={labelClassName}
@ -352,6 +361,9 @@ export default class FormItem extends React.Component<FormItemProps, any> {
</label>
</Col>
) : null;
}}
</FormContext.Consumer>
);
}
renderChildren(prefixCls: string) {

View File

@ -481,6 +481,8 @@ exports[`renders ./components/form/demo/advanced-search.md correctly 1`] = `
exports[`renders ./components/form/demo/coordinated.md correctly 1`] = `
<form
class="ant-form ant-form-horizontal"
labelcol="[object Object]"
wrappercol="[object Object]"
>
<div
class="ant-row ant-form-item"
@ -776,7 +778,10 @@ exports[`renders ./components/form/demo/dynamic-form-item.md correctly 1`] = `
width="1em"
>
<path
d="M848 474H550V152h-76v322H176c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h298v322h76V550h298c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z"
d="M482 152h60q8 0 8 8v704q0 8-8 8h-60q-8 0-8-8V160q0-8 8-8z"
/>
<path
d="M176 474h672q8 0 8 8v60q0 8-8 8H176q-8 0-8-8v-60q0-8 8-8z"
/>
</svg>
</i>
@ -1508,6 +1513,8 @@ exports[`renders ./components/form/demo/normal-login.md correctly 1`] = `
exports[`renders ./components/form/demo/register.md correctly 1`] = `
<form
class="ant-form ant-form-horizontal"
labelcol="[object Object]"
wrappercol="[object Object]"
>
<div
class="ant-row ant-form-item"
@ -2462,6 +2469,8 @@ exports[`renders ./components/form/demo/style-check-debug.md correctly 1`] = `
exports[`renders ./components/form/demo/time-related-controls.md correctly 1`] = `
<form
class="ant-form ant-form-horizontal"
labelcol="[object Object]"
wrappercol="[object Object]"
>
<div
class="ant-row ant-form-item"
@ -2880,6 +2889,8 @@ exports[`renders ./components/form/demo/time-related-controls.md correctly 1`] =
exports[`renders ./components/form/demo/validate-other.md correctly 1`] = `
<form
class="ant-form ant-form-horizontal"
labelcol="[object Object]"
wrappercol="[object Object]"
>
<div
class="ant-row ant-form-item"
@ -4096,6 +4107,8 @@ exports[`renders ./components/form/demo/validate-other.md correctly 1`] = `
exports[`renders ./components/form/demo/validate-static.md correctly 1`] = `
<form
class="ant-form ant-form-horizontal"
labelcol="[object Object]"
wrappercol="[object Object]"
>
<div
class="ant-row ant-form-item ant-form-item-with-help"

View File

@ -0,0 +1,12 @@
import createReactContext, { Context } from 'create-react-context';
import { ColProps } from '../grid/col';
export interface FormContextProps {
vertical: boolean;
labelCol?: ColProps;
wrapperCol?: ColProps;
}
export const FormContext: Context<FormContextProps> = createReactContext({
vertical: false,
});

View File

@ -40,11 +40,9 @@ class App extends React.Component {
render() {
const { getFieldDecorator } = this.props.form;
return (
<Form onSubmit={this.handleSubmit}>
<Form labelCol={{ span: 5 }} wrapperCol={{ span: 12 }} onSubmit={this.handleSubmit}>
<Form.Item
label="Note"
labelCol={{ span: 5 }}
wrapperCol={{ span: 12 }}
>
{getFieldDecorator('note', {
rules: [{ required: true, message: 'Please input your note!' }],
@ -54,8 +52,6 @@ class App extends React.Component {
</Form.Item>
<Form.Item
label="Gender"
labelCol={{ span: 5 }}
wrapperCol={{ span: 12 }}
>
{getFieldDecorator('gender', {
rules: [{ required: true, message: 'Please select your gender!' }],

View File

@ -132,9 +132,8 @@ class RegistrationForm extends React.Component {
));
return (
<Form onSubmit={this.handleSubmit}>
<Form {...formItemLayout} onSubmit={this.handleSubmit}>
<Form.Item
{...formItemLayout}
label="E-mail"
>
{getFieldDecorator('email', {
@ -148,7 +147,6 @@ class RegistrationForm extends React.Component {
)}
</Form.Item>
<Form.Item
{...formItemLayout}
label="Password"
>
{getFieldDecorator('password', {
@ -162,7 +160,6 @@ class RegistrationForm extends React.Component {
)}
</Form.Item>
<Form.Item
{...formItemLayout}
label="Confirm Password"
>
{getFieldDecorator('confirm', {
@ -176,7 +173,6 @@ class RegistrationForm extends React.Component {
)}
</Form.Item>
<Form.Item
{...formItemLayout}
label={(
<span>
Nickname&nbsp;
@ -193,7 +189,6 @@ class RegistrationForm extends React.Component {
)}
</Form.Item>
<Form.Item
{...formItemLayout}
label="Habitual Residence"
>
{getFieldDecorator('residence', {
@ -204,7 +199,6 @@ class RegistrationForm extends React.Component {
)}
</Form.Item>
<Form.Item
{...formItemLayout}
label="Phone Number"
>
{getFieldDecorator('phone', {
@ -214,7 +208,6 @@ class RegistrationForm extends React.Component {
)}
</Form.Item>
<Form.Item
{...formItemLayout}
label="Website"
>
{getFieldDecorator('website', {
@ -230,7 +223,6 @@ class RegistrationForm extends React.Component {
)}
</Form.Item>
<Form.Item
{...formItemLayout}
label="Captcha"
extra="We must make sure that your are a human."
>

View File

@ -67,9 +67,8 @@ class TimeRelatedForm extends React.Component {
rules: [{ type: 'array', required: true, message: 'Please select time!' }],
};
return (
<Form onSubmit={this.handleSubmit}>
<Form {...formItemLayout} onSubmit={this.handleSubmit}>
<Form.Item
{...formItemLayout}
label="DatePicker"
>
{getFieldDecorator('date-picker', config)(
@ -77,7 +76,6 @@ class TimeRelatedForm extends React.Component {
)}
</Form.Item>
<Form.Item
{...formItemLayout}
label="DatePicker[showTime]"
>
{getFieldDecorator('date-time-picker', config)(
@ -85,7 +83,6 @@ class TimeRelatedForm extends React.Component {
)}
</Form.Item>
<Form.Item
{...formItemLayout}
label="MonthPicker"
>
{getFieldDecorator('month-picker', config)(
@ -93,7 +90,6 @@ class TimeRelatedForm extends React.Component {
)}
</Form.Item>
<Form.Item
{...formItemLayout}
label="RangePicker"
>
{getFieldDecorator('range-picker', rangeConfig)(
@ -101,7 +97,6 @@ class TimeRelatedForm extends React.Component {
)}
</Form.Item>
<Form.Item
{...formItemLayout}
label="RangePicker[showTime]"
>
{getFieldDecorator('range-time-picker', rangeConfig)(
@ -109,7 +104,6 @@ class TimeRelatedForm extends React.Component {
)}
</Form.Item>
<Form.Item
{...formItemLayout}
label="TimePicker"
>
{getFieldDecorator('time-picker', config)(

View File

@ -47,15 +47,13 @@ class Demo extends React.Component {
wrapperCol: { span: 14 },
};
return (
<Form onSubmit={this.handleSubmit}>
<Form {...formItemLayout} onSubmit={this.handleSubmit}>
<Form.Item
{...formItemLayout}
label="Plain Text"
>
<span className="ant-form-text">China</span>
</Form.Item>
<Form.Item
{...formItemLayout}
label="Select"
hasFeedback
>
@ -72,7 +70,6 @@ class Demo extends React.Component {
</Form.Item>
<Form.Item
{...formItemLayout}
label="Select[multiple]"
>
{getFieldDecorator('select-multiple', {
@ -89,7 +86,6 @@ class Demo extends React.Component {
</Form.Item>
<Form.Item
{...formItemLayout}
label="InputNumber"
>
{getFieldDecorator('input-number', { initialValue: 3 })(
@ -99,7 +95,6 @@ class Demo extends React.Component {
</Form.Item>
<Form.Item
{...formItemLayout}
label="Switch"
>
{getFieldDecorator('switch', { valuePropName: 'checked' })(
@ -108,7 +103,6 @@ class Demo extends React.Component {
</Form.Item>
<Form.Item
{...formItemLayout}
label="Slider"
>
{getFieldDecorator('slider')(
@ -120,7 +114,6 @@ class Demo extends React.Component {
</Form.Item>
<Form.Item
{...formItemLayout}
label="Radio.Group"
>
{getFieldDecorator('radio-group')(
@ -133,7 +126,6 @@ class Demo extends React.Component {
</Form.Item>
<Form.Item
{...formItemLayout}
label="Radio.Button"
>
{getFieldDecorator('radio-button')(
@ -146,7 +138,6 @@ class Demo extends React.Component {
</Form.Item>
<Form.Item
{...formItemLayout}
label="Checkbox.Group"
>
{getFieldDecorator("checkbox-group", {
@ -165,7 +156,6 @@ class Demo extends React.Component {
</Form.Item>
<Form.Item
{...formItemLayout}
label="Rate"
>
{getFieldDecorator('rate', {
@ -176,7 +166,6 @@ class Demo extends React.Component {
</Form.Item>
<Form.Item
{...formItemLayout}
label="Upload"
extra="longgggggggggggggggggggggggggggggggggg"
>
@ -193,7 +182,6 @@ class Demo extends React.Component {
</Form.Item>
<Form.Item
{...formItemLayout}
label="Dragger"
>
<div className="dropbox">

View File

@ -40,9 +40,8 @@ const formItemLayout = {
};
ReactDOM.render(
<Form>
<Form {...formItemLayout}>
<Form.Item
{...formItemLayout}
label="Fail"
validateStatus="error"
help="Should be combination of numbers & alphabets"
@ -51,7 +50,6 @@ ReactDOM.render(
</Form.Item>
<Form.Item
{...formItemLayout}
label="Warning"
validateStatus="warning"
>
@ -59,7 +57,6 @@ ReactDOM.render(
</Form.Item>
<Form.Item
{...formItemLayout}
label="Validating"
hasFeedback
validateStatus="validating"
@ -69,7 +66,6 @@ ReactDOM.render(
</Form.Item>
<Form.Item
{...formItemLayout}
label="Success"
hasFeedback
validateStatus="success"
@ -78,7 +74,6 @@ ReactDOM.render(
</Form.Item>
<Form.Item
{...formItemLayout}
label="Warning"
hasFeedback
validateStatus="warning"
@ -87,7 +82,6 @@ ReactDOM.render(
</Form.Item>
<Form.Item
{...formItemLayout}
label="Fail"
hasFeedback
validateStatus="error"
@ -97,7 +91,6 @@ ReactDOM.render(
</Form.Item>
<Form.Item
{...formItemLayout}
label="Success"
hasFeedback
validateStatus="success"
@ -106,7 +99,6 @@ ReactDOM.render(
</Form.Item>
<Form.Item
{...formItemLayout}
label="Warning"
hasFeedback
validateStatus="warning"
@ -115,7 +107,6 @@ ReactDOM.render(
</Form.Item>
<Form.Item
{...formItemLayout}
label="Error"
hasFeedback
validateStatus="error"
@ -128,7 +119,6 @@ ReactDOM.render(
</Form.Item>
<Form.Item
{...formItemLayout}
label="Validating"
hasFeedback
validateStatus="validating"
@ -139,7 +129,6 @@ ReactDOM.render(
<Form.Item
label="inline"
{...formItemLayout}
style={{ marginBottom: 0 }}
>
<Form.Item
@ -152,13 +141,14 @@ ReactDOM.render(
<span style={{ display: 'inline-block', width: '24px', textAlign: 'center' }}>
-
</span>
<Form.Item style={{ display: 'inline-block', width: 'calc(50% - 12px)' }}>
<Form.Item
style={{ display: 'inline-block', width: 'calc(50% - 12px)' }}
>
<DatePicker />
</Form.Item>
</Form.Item>
<Form.Item
{...formItemLayout}
label="Success"
hasFeedback
validateStatus="success"

View File

@ -36,8 +36,10 @@ A form field is defined using `<Form.Item />`.
| -------- | ----------- | ---- | ------------- |
| form | Decorated by `Form.create()` will be automatically set `this.props.form` property | object | n/a |
| hideRequiredMark | Hide required mark of all form items | Boolean | false |
| labelCol | (Added in 3.14.0. Previous version can only set on FormItem.) The layout of label. You can set `span` `offset` to something like `{span: 3, offset: 12}` or `sm: {span: 3, offset: 12}` same as with `<Col>` | [object](https://ant.design/components/grid/#Col) | |
| layout | Define form layout | 'horizontal'\|'vertical'\|'inline' | 'horizontal' |
| onSubmit | Defines a function will be called if form data validation is successful. | Function(e:Event) | |
| wrapperCol | (Added in 3.14.0. Previous version can only set on FormItem.) The layout for input controls, same as `labelCol` | [object](https://ant.design/components/grid/#Col) | |
### Form.create(options)
@ -192,10 +194,10 @@ Note: if Form.Item has multiple children that had been decorated by `getFieldDec
| hasFeedback | Used with `validateStatus`, this option specifies the validation status icon. Recommended to be used only with `Input`. | boolean | false |
| help | The prompt message. If not provided, the prompt message will be generated by the validation rule. | string\|ReactNode | |
| label | Label text | string\|ReactNode | |
| labelCol | The layout of label. You can set `span` `offset` to something like `{span: 3, offset: 12}` or `sm: {span: 3, offset: 12}` same as with `<Col>` | [object](https://ant.design/components/grid/#Col) | |
| labelCol | The layout of label. You can set `span` `offset` to something like `{span: 3, offset: 12}` or `sm: {span: 3, offset: 12}` same as with `<Col>`. You can set on Form one time after 3.14.0. Will take FormItem's prop when both set with Form. | [object](https://ant.design/components/grid/#Col) | |
| required | Whether provided or not, it will be generated by the validation rule. | boolean | false |
| validateStatus | The validation status. If not provided, it will be generated by validation rule. options: 'success' 'warning' 'error' 'validating' | string | |
| wrapperCol | The layout for input controls, same as `labelCol` | [object](https://ant.design/components/grid/#Col) | |
| wrapperCol | The layout for input controls, same as `labelCol`. You can set on Form one time after 3.14.0. Will take FormItem's prop when both set with Form. | [object](https://ant.design/components/grid/#Col) | |
### Validation Rules

View File

@ -38,8 +38,10 @@ title: Form
| --- | --- | --- | --- |
| form | 经 `Form.create()` 包装过的组件会自带 `this.props.form` 属性 | object | - |
| hideRequiredMark | 隐藏所有表单项的必选标记 | Boolean | false |
| labelCol | 3.14.0 新增,之前的版本只能设置到 FormItem 上。label 标签布局,同 `<Col>` 组件,设置 `span` `offset` 值,如 `{span: 3, offset: 12}``sm: {span: 3, offset: 12}` | [object](https://ant.design/components/grid/#Col) | |
| layout | 表单布局 | 'horizontal'\|'vertical'\|'inline' | 'horizontal' |
| onSubmit | 数据验证成功后回调事件 | Function(e:Event) | |
| wrapperCol | 3.14.0 新增,之前的版本只能设置到 FormItem 上。)需要为输入控件设置布局样式时,使用该属性,用法同 labelCol | [object](https://ant.design/components/grid/#Col) | |
### Form.create(options)
@ -194,10 +196,10 @@ validateFields(['field1', 'field2'], options, (errors, values) => {
| hasFeedback | 配合 validateStatus 属性使用,展示校验状态图标,建议只配合 Input 组件使用 | boolean | false |
| help | 提示信息,如不设置,则会根据校验规则自动生成 | string\|ReactNode | |
| label | label 标签的文本 | string\|ReactNode | |
| labelCol | label 标签布局,同 `<Col>` 组件,设置 `span` `offset` 值,如 `{span: 3, offset: 12}``sm: {span: 3, offset: 12}` | [object](https://ant.design/components/grid/#Col) | |
| labelCol | label 标签布局,同 `<Col>` 组件,设置 `span` `offset` 值,如 `{span: 3, offset: 12}``sm: {span: 3, offset: 12}`。在 3.14.0 之后,你可以通过 Form 的 labelCol 进行统一设置。当和 Form 同时设置时,以 FormItem 为准。 | [object](https://ant.design/components/grid/#Col) | |
| required | 是否必填,如不设置,则会根据校验规则自动生成 | boolean | false |
| validateStatus | 校验状态,如不设置,则会根据校验规则自动生成,可选:'success' 'warning' 'error' 'validating' | string | |
| wrapperCol | 需要为输入控件设置布局样式时,使用该属性,用法同 labelCol | [object](https://ant.design/components/grid/#Col) | |
| wrapperCol | 需要为输入控件设置布局样式时,使用该属性,用法同 labelCol。在 3.14.0 之后,你可以通过 Form 的 labelCol 进行统一设置。当和 Form 同时设置时,以 FormItem 为准。 | [object](https://ant.design/components/grid/#Col) | |
### 校验规则

View File

@ -44,6 +44,7 @@ export interface IconProps {
className?: string;
theme?: ThemeType;
title?: string;
onKeyUp?: React.KeyboardEventHandler<HTMLElement>;
onClick?: React.MouseEventHandler<HTMLElement>;
component?: React.ComponentType<CustomIconComponentProps>;
twoToneColor?: string;

View File

@ -85,6 +85,8 @@ export { default as Statistic } from './statistic';
export { default as notification } from './notification';
export { default as PageHeader } from './page-header';
export { default as Pagination } from './pagination';
export { default as Popconfirm } from './popconfirm';
@ -129,6 +131,8 @@ export { default as Timeline } from './timeline';
export { default as Tooltip } from './tooltip';
export { default as Typography } from './typography';
export { default as Mention } from './mention';
export { default as Upload } from './upload';

View File

@ -1,4 +1,18 @@
import React from 'react';
import { mount } from 'enzyme';
import InputNumber from '..';
import focusTest from '../../../tests/shared/focusTest';
focusTest(InputNumber);
describe('InputNumber', () => {
focusTest(InputNumber);
// https://github.com/ant-design/ant-design/issues/13896
it('should return null when blur a empty input number', () => {
const onChange = jest.fn();
const wrapper = mount(<InputNumber defaultValue="1" onChange={onChange} />);
wrapper.find('input').simulate('change', { target: { value: '' } });
expect(onChange).toHaveBeenLastCalledWith('');
wrapper.find('input').simulate('blur');
expect(onChange).toHaveBeenLastCalledWith(null);
});
});

View File

@ -2,9 +2,9 @@ import * as React from 'react';
import omit from 'omit.js';
import classNames from 'classnames';
import { polyfill } from 'react-lifecycles-compat';
import ResizeObserver from 'resize-observer-polyfill';
import calculateNodeHeight from './calculateNodeHeight';
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
import ResizeObserver from '../_util/resizeObserver';
function onNextFrame(cb: () => void) {
if (window.requestAnimationFrame) {
@ -40,7 +40,6 @@ export interface TextAreaState {
class TextArea extends React.Component<TextAreaProps, TextAreaState> {
nextFrameActionId: number;
resizeObserver: ResizeObserver | null;
state = {
textareaStyles: {},
@ -50,7 +49,6 @@ class TextArea extends React.Component<TextAreaProps, TextAreaState> {
componentDidMount() {
this.resizeTextarea();
this.updateResizeObserverHook();
}
componentDidUpdate(prevProps: TextAreaProps) {
@ -58,13 +56,6 @@ class TextArea extends React.Component<TextAreaProps, TextAreaState> {
if (prevProps.value !== this.props.value) {
this.resizeOnNextFrame();
}
this.updateResizeObserverHook();
}
componentWillUnmount() {
if (this.resizeObserver) {
this.resizeObserver.disconnect();
}
}
resizeOnNextFrame = () => {
@ -74,19 +65,6 @@ class TextArea extends React.Component<TextAreaProps, TextAreaState> {
this.nextFrameActionId = onNextFrame(this.resizeTextarea);
};
// We will update hooks if `autosize` prop change
updateResizeObserverHook() {
if (!this.resizeObserver && this.props.autosize) {
// Add resize observer
this.resizeObserver = new ResizeObserver(this.resizeOnNextFrame);
this.resizeObserver.observe(this.textAreaRef);
} else if (this.resizeObserver && !this.props.autosize) {
// Remove resize observer
this.resizeObserver.disconnect();
this.resizeObserver = null;
}
}
focus() {
this.textAreaRef.focus();
}
@ -130,7 +108,7 @@ class TextArea extends React.Component<TextAreaProps, TextAreaState> {
};
renderTextArea = ({ getPrefixCls }: ConfigConsumerProps) => {
const { prefixCls: customizePrefixCls, className, disabled } = this.props;
const { prefixCls: customizePrefixCls, className, disabled, autosize } = this.props;
const { ...props } = this.props;
const otherProps = omit(props, ['prefixCls', 'onPressEnter', 'autosize']);
const prefixCls = getPrefixCls('input', customizePrefixCls);
@ -148,6 +126,7 @@ class TextArea extends React.Component<TextAreaProps, TextAreaState> {
otherProps.value = otherProps.value || '';
}
return (
<ResizeObserver onResize={this.resizeOnNextFrame} disabled={!autosize}>
<textarea
{...otherProps}
className={cls}
@ -156,6 +135,7 @@ class TextArea extends React.Component<TextAreaProps, TextAreaState> {
onChange={this.handleTextareaChange}
ref={this.saveTextAreaRef}
/>
</ResizeObserver>
);
};

View File

@ -625,6 +625,10 @@ exports[`TextArea should support disabled 1`] = `
disabled={true}
>
<Consumer>
<ReactResizeObserver
disabled={true}
onResize={[Function]}
>
<textarea
className="ant-input ant-input-disabled"
disabled={true}
@ -632,6 +636,7 @@ exports[`TextArea should support disabled 1`] = `
onKeyDown={[Function]}
style={Object {}}
/>
</ReactResizeObserver>
</Consumer>
</TextArea>
`;
@ -641,6 +646,10 @@ exports[`TextArea should support maxLength 1`] = `
maxLength={10}
>
<Consumer>
<ReactResizeObserver
disabled={true}
onResize={[Function]}
>
<textarea
className="ant-input"
maxLength={10}
@ -648,6 +657,7 @@ exports[`TextArea should support maxLength 1`] = `
onKeyDown={[Function]}
style={Object {}}
/>
</ReactResizeObserver>
</Consumer>
</TextArea>
`;

View File

@ -254,10 +254,10 @@ class Sider extends React.Component<SiderProps, SiderState> {
[`${prefixCls}-zero-width`]: parseFloat(siderWidth) === 0,
});
return (
<div className={siderCls} {...divProps} style={divStyle}>
<aside className={siderCls} {...divProps} style={divStyle}>
<div className={`${prefixCls}-children`}>{this.props.children}</div>
{collapsible || (this.state.below && zeroWidthTrigger) ? triggerDom : null}
</div>
</aside>
);
};

View File

@ -2,37 +2,37 @@
exports[`renders ./components/layout/demo/basic.md correctly 1`] = `
<div>
<div
<section
class="ant-layout"
>
<div
<header
class="ant-layout-header"
>
Header
</div>
<div
</header>
<main
class="ant-layout-content"
>
Content
</div>
<div
</main>
<footer
class="ant-layout-footer"
>
Footer
</div>
</div>
<div
</footer>
</section>
<section
class="ant-layout"
>
<div
<header
class="ant-layout-header"
>
Header
</div>
<div
</header>
<section
class="ant-layout"
>
<div
<aside
class="ant-layout-sider ant-layout-sider-dark"
style="flex:0 0 200px;max-width:200px;min-width:200px;width:200px"
>
@ -41,36 +41,36 @@ exports[`renders ./components/layout/demo/basic.md correctly 1`] = `
>
Sider
</div>
</div>
<div
</aside>
<main
class="ant-layout-content"
>
Content
</div>
</div>
<div
</main>
</section>
<footer
class="ant-layout-footer"
>
Footer
</div>
</div>
<div
</footer>
</section>
<section
class="ant-layout"
>
<div
<header
class="ant-layout-header"
>
Header
</div>
<div
</header>
<section
class="ant-layout"
>
<div
<main
class="ant-layout-content"
>
Content
</div>
<div
</main>
<aside
class="ant-layout-sider ant-layout-sider-dark"
style="flex:0 0 200px;max-width:200px;min-width:200px;width:200px"
>
@ -79,18 +79,18 @@ exports[`renders ./components/layout/demo/basic.md correctly 1`] = `
>
Sider
</div>
</div>
</div>
<div
</aside>
</section>
<footer
class="ant-layout-footer"
>
Footer
</div>
</div>
<div
</footer>
</section>
<section
class="ant-layout"
>
<div
<aside
class="ant-layout-sider ant-layout-sider-dark"
style="flex:0 0 200px;max-width:200px;min-width:200px;width:200px"
>
@ -99,35 +99,35 @@ exports[`renders ./components/layout/demo/basic.md correctly 1`] = `
>
Sider
</div>
</div>
<div
</aside>
<section
class="ant-layout"
>
<div
<header
class="ant-layout-header"
>
Header
</div>
<div
</header>
<main
class="ant-layout-content"
>
Content
</div>
<div
</main>
<footer
class="ant-layout-footer"
>
Footer
</div>
</div>
</div>
</footer>
</section>
</section>
</div>
`;
exports[`renders ./components/layout/demo/custom-trigger.md correctly 1`] = `
<div
<section
class="ant-layout"
>
<div
<aside
class="ant-layout-sider ant-layout-sider-dark"
style="flex:0 0 200px;max-width:200px;min-width:200px;width:200px"
>
@ -224,11 +224,11 @@ exports[`renders ./components/layout/demo/custom-trigger.md correctly 1`] = `
</li>
</ul>
</div>
</div>
<div
</aside>
<section
class="ant-layout"
>
<div
<header
class="ant-layout-header"
style="background:#fff;padding:0"
>
@ -251,22 +251,22 @@ exports[`renders ./components/layout/demo/custom-trigger.md correctly 1`] = `
/>
</svg>
</i>
</div>
<div
</header>
<main
class="ant-layout-content"
style="margin:24px 16px;padding:24px;background:#fff;min-height:280px"
>
Content
</div>
</div>
</div>
</main>
</section>
</section>
`;
exports[`renders ./components/layout/demo/fixed.md correctly 1`] = `
<div
<section
class="ant-layout"
>
<div
<header
class="ant-layout-header"
style="position:fixed;z-index:1;width:100%"
>
@ -369,8 +369,8 @@ exports[`renders ./components/layout/demo/fixed.md correctly 1`] = `
</div>
</li>
</ul>
</div>
<div
</header>
<main
class="ant-layout-content"
style="padding:0 50px;margin-top:64px"
>
@ -420,21 +420,21 @@ exports[`renders ./components/layout/demo/fixed.md correctly 1`] = `
>
Content
</div>
</div>
<div
</main>
<footer
class="ant-layout-footer"
style="text-align:center"
>
Ant Design ©2018 Created by Ant UED
</div>
</div>
</footer>
</section>
`;
exports[`renders ./components/layout/demo/fixed-sider.md correctly 1`] = `
<div
<section
class="ant-layout"
>
<div
<aside
class="ant-layout-sider ant-layout-sider-dark"
style="overflow:auto;height:100vh;position:fixed;left:0;flex:0 0 200px;max-width:200px;min-width:200px;width:200px"
>
@ -682,16 +682,16 @@ exports[`renders ./components/layout/demo/fixed-sider.md correctly 1`] = `
</li>
</ul>
</div>
</div>
<div
</aside>
<section
class="ant-layout"
style="margin-left:200px"
>
<div
<header
class="ant-layout-header"
style="background:#fff;padding:0"
/>
<div
<main
class="ant-layout-content"
style="margin:24px 16px 0;overflow:initial"
>
@ -794,22 +794,22 @@ exports[`renders ./components/layout/demo/fixed-sider.md correctly 1`] = `
<br />
content
</div>
</div>
<div
</main>
<footer
class="ant-layout-footer"
style="text-align:center"
>
Ant Design ©2018 Created by Ant UED
</div>
</div>
</div>
</footer>
</section>
</section>
`;
exports[`renders ./components/layout/demo/responsive.md correctly 1`] = `
<div
<section
class="ant-layout"
>
<div
<aside
class="ant-layout-sider ant-layout-sider-dark"
style="flex:0 0 200px;max-width:200px;min-width:200px;width:200px"
>
@ -941,15 +941,15 @@ exports[`renders ./components/layout/demo/responsive.md correctly 1`] = `
</li>
</ul>
</div>
</div>
<div
</aside>
<section
class="ant-layout"
>
<div
<header
class="ant-layout-header"
style="background:#fff;padding:0"
/>
<div
<main
class="ant-layout-content"
style="margin:24px 16px 0"
>
@ -958,23 +958,23 @@ exports[`renders ./components/layout/demo/responsive.md correctly 1`] = `
>
content
</div>
</div>
<div
</main>
<footer
class="ant-layout-footer"
style="text-align:center"
>
Ant Design ©2018 Created by Ant UED
</div>
</div>
</div>
</footer>
</section>
</section>
`;
exports[`renders ./components/layout/demo/side.md correctly 1`] = `
<div
<section
class="ant-layout"
style="min-height:100vh"
>
<div
<aside
class="ant-layout-sider ant-layout-sider-dark ant-layout-sider-has-trigger"
style="flex:0 0 200px;max-width:200px;min-width:200px;width:200px"
>
@ -1172,15 +1172,15 @@ exports[`renders ./components/layout/demo/side.md correctly 1`] = `
</svg>
</i>
</div>
</div>
<div
</aside>
<section
class="ant-layout"
>
<div
<header
class="ant-layout-header"
style="background:#fff;padding:0"
/>
<div
<main
class="ant-layout-content"
style="margin:0 16px"
>
@ -1218,22 +1218,22 @@ exports[`renders ./components/layout/demo/side.md correctly 1`] = `
>
Bill is a cat.
</div>
</div>
<div
</main>
<footer
class="ant-layout-footer"
style="text-align:center"
>
Ant Design ©2018 Created by Ant UED
</div>
</div>
</div>
</footer>
</section>
</section>
`;
exports[`renders ./components/layout/demo/top.md correctly 1`] = `
<div
<section
class="layout ant-layout"
>
<div
<header
class="ant-layout-header"
>
<div
@ -1335,8 +1335,8 @@ exports[`renders ./components/layout/demo/top.md correctly 1`] = `
</div>
</li>
</ul>
</div>
<div
</header>
<main
class="ant-layout-content"
style="padding:0 50px"
>
@ -1386,21 +1386,21 @@ exports[`renders ./components/layout/demo/top.md correctly 1`] = `
>
Content
</div>
</div>
<div
</main>
<footer
class="ant-layout-footer"
style="text-align:center"
>
Ant Design ©2018 Created by Ant UED
</div>
</div>
</footer>
</section>
`;
exports[`renders ./components/layout/demo/top-side.md correctly 1`] = `
<div
<section
class="ant-layout"
>
<div
<header
class="header ant-layout-header"
>
<div
@ -1502,8 +1502,8 @@ exports[`renders ./components/layout/demo/top-side.md correctly 1`] = `
</div>
</li>
</ul>
</div>
<div
</header>
<main
class="ant-layout-content"
style="padding:0 50px"
>
@ -1548,11 +1548,11 @@ exports[`renders ./components/layout/demo/top-side.md correctly 1`] = `
</span>
</span>
</div>
<div
<section
class="ant-layout"
style="padding:24px 0;background:#fff"
>
<div
<aside
class="ant-layout-sider ant-layout-sider-dark"
style="background:#fff;flex:0 0 200px;max-width:200px;min-width:200px;width:200px"
>
@ -1711,29 +1711,29 @@ exports[`renders ./components/layout/demo/top-side.md correctly 1`] = `
</li>
</ul>
</div>
</div>
<div
</aside>
<main
class="ant-layout-content"
style="padding:0 24px;min-height:280px"
>
Content
</div>
</div>
</div>
<div
</main>
</section>
</main>
<footer
class="ant-layout-footer"
style="text-align:center"
>
Ant Design ©2018 Created by Ant UED
</div>
</div>
</footer>
</section>
`;
exports[`renders ./components/layout/demo/top-side-2.md correctly 1`] = `
<div
<section
class="ant-layout"
>
<div
<header
class="header ant-layout-header"
>
<div
@ -1835,11 +1835,11 @@ exports[`renders ./components/layout/demo/top-side-2.md correctly 1`] = `
</div>
</li>
</ul>
</div>
<div
</header>
<section
class="ant-layout"
>
<div
<aside
class="ant-layout-sider ant-layout-sider-dark"
style="background:#fff;flex:0 0 200px;max-width:200px;min-width:200px;width:200px"
>
@ -1998,8 +1998,8 @@ exports[`renders ./components/layout/demo/top-side-2.md correctly 1`] = `
</li>
</ul>
</div>
</div>
<div
</aside>
<section
class="ant-layout"
style="padding:0 24px 24px"
>
@ -2044,13 +2044,13 @@ exports[`renders ./components/layout/demo/top-side-2.md correctly 1`] = `
</span>
</span>
</div>
<div
<main
class="ant-layout-content"
style="background:#fff;padding:24px;margin:0;min-height:280px"
>
Content
</div>
</div>
</div>
</div>
</main>
</section>
</section>
</section>
`;

View File

@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Layout renders string width correctly 1`] = `
<div
<aside
class="ant-layout-sider ant-layout-sider-dark"
style="flex:0 0 200px;max-width:200px;min-width:200px;width:200px"
>
@ -10,5 +10,5 @@ exports[`Layout renders string width correctly 1`] = `
>
Sider
</div>
</div>
</aside>
`;

View File

@ -6,13 +6,15 @@ import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
export interface GeneratorProps {
suffixCls: string;
tagName: 'header' | 'footer' | 'main' | 'section';
}
export interface BasicProps extends React.HTMLAttributes<HTMLDivElement> {
prefixCls?: string;
hasSider?: boolean;
tagName: 'header' | 'footer' | 'main' | 'section';
}
function generator({ suffixCls }: GeneratorProps) {
function generator({ suffixCls, tagName }: GeneratorProps) {
return (BasicComponent: React.ComponentClass<BasicProps>): any => {
return class Adapter extends React.Component<BasicProps, any> {
static Header: any;
@ -24,7 +26,7 @@ function generator({ suffixCls }: GeneratorProps) {
const { prefixCls: customizePrefixCls } = this.props;
const prefixCls = getPrefixCls(suffixCls, customizePrefixCls);
return <BasicComponent prefixCls={prefixCls} {...this.props} />;
return <BasicComponent prefixCls={prefixCls} tagName={tagName} {...this.props} />;
};
render() {
@ -36,12 +38,12 @@ function generator({ suffixCls }: GeneratorProps) {
class Basic extends React.Component<BasicProps, any> {
render() {
const { prefixCls, className, children, ...others } = this.props;
const divCls = classNames(className, prefixCls);
const { prefixCls, className, children, tagName: CustomElement, ...others } = this.props;
const classString = classNames(className, prefixCls);
return (
<div className={divCls} {...others}>
<CustomElement className={classString} {...others}>
{children}
</div>
</CustomElement>
);
}
}
@ -74,14 +76,14 @@ class BasicLayout extends React.Component<BasicProps, BasicLayoutState> {
}
render() {
const { prefixCls, className, children, hasSider, ...others } = this.props;
const divCls = classNames(className, prefixCls, {
const { prefixCls, className, children, hasSider, tagName: CustomElement, ...others } = this.props;
const classString = classNames(className, prefixCls, {
[`${prefixCls}-has-sider`]: hasSider || this.state.siders.length > 0,
});
return (
<div className={divCls} {...others}>
<CustomElement className={classString} {...others}>
{children}
</div>
</CustomElement>
);
}
}
@ -93,18 +95,22 @@ const Layout: React.ComponentClass<BasicProps> & {
Sider: React.ComponentClass<SiderProps>;
} = generator({
suffixCls: 'layout',
tagName: 'section',
})(BasicLayout);
const Header = generator({
suffixCls: 'layout-header',
tagName: 'header',
})(Basic);
const Footer = generator({
suffixCls: 'layout-footer',
tagName: 'footer',
})(Basic);
const Content = generator({
suffixCls: 'layout-content',
tagName: 'main',
})(Basic);
Layout.Header = Header;

View File

@ -47,4 +47,10 @@ export default {
Icon: {
icon: 'icon',
},
Text: {
edit: 'edit',
copy: 'copy',
copied: 'copy success',
expand: 'expand',
},
};

View File

@ -47,4 +47,10 @@ export default {
Icon: {
icon: '图标',
},
Text: {
edit: '编辑',
copy: '复制',
copied: '复制成功',
expand: '展开',
},
};

View File

@ -1,5 +1,5 @@
import Modal from '..';
import { destroyFns } from '../Modal'
import { destroyFns } from '../Modal';
const { confirm } = Modal;
@ -185,15 +185,15 @@ describe('Modal.confirm triggers callbacks correctly', () => {
title: 'title',
content: 'content',
});
instances.push(instance)
instances.push(instance);
});
const { length } = instances
const { length } = instances;
instances.forEach((instance, index) => {
expect(destroyFns.length).toBe(length - index);
instance.destroy();
jest.runAllTimers();
expect(destroyFns.length).toBe(length - index - 1);
})
});
jest.useRealTimers();
});
});

View File

@ -0,0 +1,636 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders ./components/page-header/demo/actions.md correctly 1`] = `
<div
class="ant-page-header ant-page-header-has-footer"
>
<div
class="ant-page-header-back-icon"
>
<i
aria-label="icon: arrow-left"
class="anticon anticon-arrow-left"
>
<svg
aria-hidden="true"
class=""
data-icon="arrow-left"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M872 474H286.9l350.2-304c5.6-4.9 2.2-14-5.2-14h-88.5c-3.9 0-7.6 1.4-10.5 3.9L155 487.8a31.96 31.96 0 0 0 0 48.3L535.1 866c1.5 1.3 3.3 2 5.2 2h91.5c7.4 0 10.8-9.2 5.2-14L286.9 550H872c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z"
/>
</svg>
</i>
<div
class="ant-divider ant-divider-vertical"
/>
</div>
<div
class="ant-page-header-title-view"
>
<span
class="ant-page-header-title-view-title"
>
Title
</span>
<span
class="ant-page-header-title-view-sub-title"
>
This is a subtitle
</span>
<span
class="ant-page-header-title-view-tags"
>
<div
class="ant-tag ant-tag-red"
>
Warning
</div>
</span>
<span
class="ant-page-header-title-view-extra"
>
<button
class="ant-btn"
type="button"
>
<span>
Operation
</span>
</button>
<button
class="ant-btn"
type="button"
>
<span>
Operation
</span>
</button>
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
Primary
</span>
</button>
</span>
</div>
<div
class="ant-page-header-content-view"
>
<div
class="wrap"
>
<div
class="content padding"
>
<div
class="ant-row"
>
<div
class="ant-col-12"
>
<div
class="description"
>
<div
class="term"
>
Created
</div>
<div
class="detail"
>
Lili Qu
</div>
</div>
</div>
<div
class="ant-col-12"
>
<div
class="description"
>
<div
class="term"
>
Association
</div>
<div
class="detail"
>
<a>
421421
</a>
</div>
</div>
</div>
<div
class="ant-col-12"
>
<div
class="description"
>
<div
class="term"
>
Creation Time
</div>
<div
class="detail"
>
2017-01-10
</div>
</div>
</div>
<div
class="ant-col-12"
>
<div
class="description"
>
<div
class="term"
>
Effective Time
</div>
<div
class="detail"
>
2017-10-10
</div>
</div>
</div>
<div
class="ant-col-24"
>
<div
class="description"
>
<div
class="term"
>
Remarks
</div>
<div
class="detail"
>
Gonghu Road, Xihu District, Hangzhou, Zhejiang, China
</div>
</div>
</div>
</div>
</div>
<div
class="extraContent"
>
<div
class="ant-row"
>
<div
class="ant-col-12"
>
<div
class="ant-statistic"
>
<div
class="ant-statistic-title"
>
Status
</div>
<div
class="ant-statistic-content"
>
<span
class="ant-statistic-content-value"
>
Pending
</span>
</div>
</div>
</div>
<div
class="ant-col-12"
>
<div
class="ant-statistic"
>
<div
class="ant-statistic-title"
>
Price
</div>
<div
class="ant-statistic-content"
>
<span
class="ant-statistic-content-prefix"
>
$
</span>
<span
class="ant-statistic-content-value"
>
<span
class="ant-statistic-content-value-int"
>
568
</span>
<span
class="ant-statistic-content-value-decimal"
>
.08
</span>
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div
class="ant-page-header-footer"
>
<div
class="ant-tabs ant-tabs-top ant-tabs-line"
>
<div
class="ant-tabs-bar ant-tabs-top-bar"
role="tablist"
tabindex="0"
>
<div
class="ant-tabs-nav-container"
>
<span
class="ant-tabs-tab-prev ant-tabs-tab-btn-disabled"
unselectable="unselectable"
>
<span
class="ant-tabs-tab-prev-icon"
>
<i
aria-label="icon: left"
class="anticon anticon-left ant-tabs-tab-prev-icon-target"
>
<svg
aria-hidden="true"
class=""
data-icon="left"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8a31.86 31.86 0 0 0 0 50.3l450.8 352.1c5.3 4.1 12.9.4 12.9-6.3v-77.3c0-4.9-2.3-9.6-6.1-12.6l-360-281 360-281.1c3.8-3 6.1-7.7 6.1-12.6z"
/>
</svg>
</i>
</span>
</span>
<span
class="ant-tabs-tab-next ant-tabs-tab-btn-disabled"
unselectable="unselectable"
>
<span
class="ant-tabs-tab-next-icon"
>
<i
aria-label="icon: right"
class="anticon anticon-right ant-tabs-tab-next-icon-target"
>
<svg
aria-hidden="true"
class=""
data-icon="right"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M765.7 486.8L314.9 134.7A7.97 7.97 0 0 0 302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 0 0 0-50.4z"
/>
</svg>
</i>
</span>
</span>
<div
class="ant-tabs-nav-wrap"
>
<div
class="ant-tabs-nav-scroll"
>
<div
class="ant-tabs-nav ant-tabs-nav-animated"
>
<div>
<div
aria-disabled="false"
aria-selected="true"
class="ant-tabs-tab-active ant-tabs-tab"
role="tab"
>
Details
</div>
<div
aria-disabled="false"
aria-selected="false"
class=" ant-tabs-tab"
role="tab"
>
Rule
</div>
</div>
<div
class="ant-tabs-ink-bar ant-tabs-ink-bar-animated"
/>
</div>
</div>
</div>
</div>
</div>
<div
role="presentation"
style="width:0;height:0;overflow:hidden;position:absolute"
tabindex="0"
/>
<div
class="ant-tabs-content ant-tabs-content-animated ant-tabs-top-content"
style="margin-left:0%"
>
<div
aria-hidden="false"
class="ant-tabs-tabpane ant-tabs-tabpane-active"
role="tabpanel"
>
<div
role="presentation"
style="width:0;height:0;overflow:hidden;position:absolute"
tabindex="0"
/>
<div
role="presentation"
style="width:0;height:0;overflow:hidden;position:absolute"
tabindex="0"
/>
</div>
<div
aria-hidden="true"
class="ant-tabs-tabpane ant-tabs-tabpane-inactive"
role="tabpanel"
/>
</div>
<div
role="presentation"
style="width:0;height:0;overflow:hidden;position:absolute"
tabindex="0"
/>
</div>
</div>
</div>
`;
exports[`renders ./components/page-header/demo/basic.md correctly 1`] = `
<div
class="ant-page-header"
>
<div
class="ant-page-header-back-icon"
>
<i
aria-label="icon: arrow-left"
class="anticon anticon-arrow-left"
>
<svg
aria-hidden="true"
class=""
data-icon="arrow-left"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M872 474H286.9l350.2-304c5.6-4.9 2.2-14-5.2-14h-88.5c-3.9 0-7.6 1.4-10.5 3.9L155 487.8a31.96 31.96 0 0 0 0 48.3L535.1 866c1.5 1.3 3.3 2 5.2 2h91.5c7.4 0 10.8-9.2 5.2-14L286.9 550H872c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z"
/>
</svg>
</i>
<div
class="ant-divider ant-divider-vertical"
/>
</div>
<div
class="ant-page-header-title-view"
>
<span
class="ant-page-header-title-view-title"
>
Title
</span>
<span
class="ant-page-header-title-view-sub-title"
>
This is a subtitle
</span>
</div>
</div>
`;
exports[`renders ./components/page-header/demo/breadcrumb.md correctly 1`] = `
<div
class="ant-page-header"
>
<div
class="ant-breadcrumb"
>
<span>
<span
class="ant-breadcrumb-link"
>
<a
href="#/index"
>
First-level Menu
</a>
</span>
<span
class="ant-breadcrumb-separator"
>
/
</span>
</span>
<span>
<span
class="ant-breadcrumb-link"
>
<a
href="#/index/first"
>
Second-level Menu
</a>
</span>
<span
class="ant-breadcrumb-separator"
>
/
</span>
</span>
<span>
<span
class="ant-breadcrumb-link"
>
<span>
Third-level Menu
</span>
</span>
<span
class="ant-breadcrumb-separator"
>
/
</span>
</span>
</div>
<div
class="ant-page-header-title-view"
>
<span
class="ant-page-header-title-view-title"
>
Title
</span>
</div>
</div>
`;
exports[`renders ./components/page-header/demo/content.md correctly 1`] = `
<div
class="ant-page-header"
>
<div
class="ant-breadcrumb"
>
<span>
<span
class="ant-breadcrumb-link"
>
<a
href="#/index"
>
First-level Menu
</a>
</span>
<span
class="ant-breadcrumb-separator"
>
/
</span>
</span>
<span>
<span
class="ant-breadcrumb-link"
>
<a
href="#/index/first"
>
Second-level Menu
</a>
</span>
<span
class="ant-breadcrumb-separator"
>
/
</span>
</span>
<span>
<span
class="ant-breadcrumb-link"
>
<span>
Third-level Menu
</span>
</span>
<span
class="ant-breadcrumb-separator"
>
/
</span>
</span>
</div>
<div
class="ant-page-header-title-view"
>
<span
class="ant-page-header-title-view-title"
>
Title
</span>
</div>
<div
class="ant-page-header-content-view"
>
<div
class="wrap"
>
<div
class="content"
>
<div
class="content"
>
<div
class="ant-typography"
>
Ant Design interprets the color system into two levels: a system-level color system and a product-level color system.
</div>
<div
class="ant-typography"
>
Ant Design's design team preferred to design with the HSB color model, which makes it easier for designers to have a clear psychological expectation of color when adjusting colors, as well as facilitate communication in teams.
</div>
<p
class="contentLink"
>
<a>
<img
alt="start"
src="https://gw.alipayobjects.com/zos/rmsportal/MjEImQtenlyueSmVEfUD.svg"
/>
Quick Start
</a>
<a>
<img
alt="info"
src="https://gw.alipayobjects.com/zos/rmsportal/NbuDUAuBlIApFuDvWiND.svg"
/>
Product Info
</a>
<a>
<img
alt="doc"
src="https://gw.alipayobjects.com/zos/rmsportal/ohOEPSYdDTNnyMbGuyLb.svg"
/>
Product Doc
</a>
</p>
</div>
</div>
<div
class="extraContent"
>
<img
alt="content"
src="https://gw.alipayobjects.com/mdn/mpaas_user/afts/img/A*KsfVQbuLRlYAAAAAAAAAAABjAQAAAQ/original"
/>
</div>
</div>
</div>
</div>
`;

View File

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

View File

@ -0,0 +1,41 @@
import React from 'react';
import { mount } from 'enzyme';
import PageHeader from '..';
describe('PageHeader', () => {
it('pageHeader should not contain back it back', () => {
const routes = [
{
path: 'index',
breadcrumbName: 'First-level Menu',
},
{
path: 'first',
breadcrumbName: 'Second-level Menu',
},
{
path: 'second',
breadcrumbName: 'Third-level Menu',
},
];
const wrapper = mount(<PageHeader title="Page Title" breadcrumb={{ routes }} />);
expect(wrapper.find('.ant-page-header-back-icon')).toHaveLength(0);
});
it('pageHeader should no contain back it back', () => {
const wrapper = mount(<PageHeader title="Page Title" backIcon={false} />);
expect(wrapper.find('.ant-page-header-back-icon')).toHaveLength(0);
});
it('pageHeader should contain back it back', () => {
const callback = jest.fn(() => true);
const wrapper = mount(<PageHeader title="Page Title" onBack={callback} />);
expect(wrapper.find('.ant-page-header-back-icon')).toHaveLength(1);
});
it('pageHeader onBack transfer', () => {
const callback = jest.fn(() => true);
const wrapper = mount(<PageHeader title="Page Title" onBack={callback} />);
wrapper.find('.ant-page-header-back-icon').simulate('click');
expect(callback).toBeCalled();
});
});

View File

@ -0,0 +1,121 @@
---
order: 5
title:
zh-CN: 复杂的例子
en-US: Complex example
---
## zh-CN
使用操作区,并自定义子节点,适合使用在需要展示一些复杂的信息,帮助用户快速了解这个页面的信息和操作。
## en-US
Use the operating area and customize the sub-nodes, suitable for use in the need to display some complex information to help users quickly understand the information and operations of this page.
```jsx
import { PageHeader, Tag, Tabs, Button, Statistic, Row, Col } from 'antd';
const TabPane = Tabs.TabPane;
const Description = ({ term, children, span = 12 }) => (
<Col span={span}>
<div className="description">
<div className="term">{term}</div>
<div className="detail">{children}</div>
</div>
</Col>
);
const content = (
<Row>
<Description term="Created">Lili Qu</Description>
<Description term="Association">
<a>421421</a>
</Description>
<Description term="Creation Time">2017-01-10</Description>
<Description term="Effective Time">2017-10-10</Description>
<Description term="Remarks" span={24}>
Gonghu Road, Xihu District, Hangzhou, Zhejiang, China
</Description>
</Row>
);
const extraContent = (
<Row>
<Col span={12}>
<Statistic title="Status" value="Pending" />
</Col>
<Col span={12}>
<Statistic title="Price" prefix="$" value={568.08} />
</Col>
</Row>
);
ReactDOM.render(
<PageHeader
onBack={() => window.history.back()}
title="Title"
subTitle="This is a subtitle"
tags={<Tag color="red">Warning</Tag>}
extra={[
<Button key="3">Operation</Button>,
<Button key="2">Operation</Button>,
<Button key="1" type="primary">
Primary
</Button>,
]}
footer={
<Tabs defaultActiveKey="1">
<TabPane tab="Details" key="1" />
<TabPane tab="Rule" key="2" />
</Tabs>
}
>
<div className="wrap">
<div className="content padding">{content}</div>
<div className="extraContent">{extraContent}</div>
</div>
</PageHeader>,
mountNode
);
```
<style>
#components-page-header-demo-actions .wrap {
display: flex;
}
#components-page-header-demo-actions .content {
flex: 1;
}
#components-page-header-demo-actions .extraContent {
min-width: 240px;
text-align: right;
}
#components-page-header-demo-actions .content.padding {
padding-left: 40px;
}
#components-page-header-demo-actions .content .description {
display: table;
}
#components-page-header-demo-actions .description .term {
display: table-cell;
margin-right: 8px;
padding-bottom: 8px;
white-space: nowrap;
line-height: 20px;
}
#components-page-header-demo-actions .description .term:after {
position: relative;
top: -0.5px;
margin: 0 8px 0 2px;
content: ":";
}
#components-page-header-demo-actions .description .detail {
display: table-cell;
padding-bottom: 8px;
width: 100%;
line-height: 20px;
}
</style>

View File

@ -0,0 +1,34 @@
---
order: 1
title:
zh-CN: 标准样式
en-US: Basic Page Header
---
## zh-CN
标准页头,适合使用在需要简单描述的场景。
## en-US
Standard header, suitable for use in scenarios that require a brief description.
```jsx
import { PageHeader } from 'antd';
ReactDOM.render(
<PageHeader
onBack={() => null}
title="Title"
subTitle="This is a subtitle"
/>,
mountNode
);
```
<style>
.code-box-demo .ant-page-header {
border: 1px solid rgb(235, 237, 240);
}
<style>

View File

@ -0,0 +1,42 @@
---
order: 2
title:
zh-CN: 带面包屑页头
en-US: Use with breadcrumbs
---
## zh-CN
带面包屑页头,适合层级比较深的页面,让用户可以快速导航。
## en-US
With breadcrumbs, it is suitable for deeper pages, allowing users to navigate quickly.
```jsx
import { PageHeader } from 'antd';
const routes = [
{
path: 'index',
breadcrumbName: 'First-level Menu',
},
{
path: 'first',
breadcrumbName: 'Second-level Menu',
},
{
path: 'second',
breadcrumbName: 'Third-level Menu',
},
];
ReactDOM.render(
<PageHeader
title="Title"
breadcrumb={{ routes }}
/>,
mountNode
);
```

View File

@ -0,0 +1,115 @@
---
order: 3
title:
zh-CN: 带内容的例子
en-US: Example with content
---
## zh-CN
带内容的例子,可以优先展示页面的主要信息。
## en-US
An example with content that gives priority to the main information of the page.
```jsx
import { PageHeader,Typography } from 'antd';
const { Paragraph } = Typography;
const routes = [
{
path: 'index',
breadcrumbName: 'First-level Menu',
},
{
path: 'first',
breadcrumbName: 'Second-level Menu',
},
{
path: 'second',
breadcrumbName: 'Third-level Menu',
},
];
const content = (
<div className="content">
<Paragraph>
Ant Design interprets the color system into two levels: a system-level
color system and a product-level color system.
</Paragraph>
<Paragraph>
Ant Design&#x27;s design team preferred to design with the HSB color model,
which makes it easier for designers to have a clear psychological
expectation of color when adjusting colors, as well as facilitate
communication in teams.
</Paragraph>
<p className="contentLink">
<a>
<img
src="https://gw.alipayobjects.com/zos/rmsportal/MjEImQtenlyueSmVEfUD.svg"
alt="start"
/>
Quick Start
</a>
<a>
<img
src="https://gw.alipayobjects.com/zos/rmsportal/NbuDUAuBlIApFuDvWiND.svg"
alt="info"
/>
Product Info
</a>
<a>
<img
src="https://gw.alipayobjects.com/zos/rmsportal/ohOEPSYdDTNnyMbGuyLb.svg"
alt="doc"
/>
Product Doc
</a>
</p>
</div>
);
const extraContent = (
<img
src="https://gw.alipayobjects.com/mdn/mpaas_user/afts/img/A*KsfVQbuLRlYAAAAAAAAAAABjAQAAAQ/original"
alt="content"
/>
);
ReactDOM.render(
<PageHeader title="Title" breadcrumb={{ routes }}>
<div className="wrap">
<div className="content">{content}</div>
<div className="extraContent">{extraContent}</div>
</div>
</PageHeader>,
mountNode,
);
```
<style>
#components-page-header-demo-content .wrap {
display: flex;
}
#components-page-header-demo-content .content {
flex: 1;
}
#components-page-header-demo-content .extraContent {
min-width: 240px;
text-align: right;
}
#components-page-header-demo-content .contentLink {
padding-top: 16px;
}
#components-page-header-demo-content .contentLink a {
display: inline-block;
vertical-align: text-top;
margin-right: 32px;
}
#components-page-header-demo-content .contentLink a img {
margin-right: 8px;
}
</style>

View File

@ -0,0 +1,27 @@
---
category: Components
type: Navigation
title: PageHeader
cols: 1
subtitle:
---
The header can be used to declare the page topic, display important information about the page that the user is interested in, and carry the action items related to the current page (including page-level operations, inter-page navigation, etc.)
## When To Use
It can also be used as inter-page navigation when it is needed to make the user quickly understand what the current page is and to facilitate the user to use the page function.
## API
| Param | Description | Type | Default value |
| ----- | ----------- | ---- | ------------- |
| title | custom title text | ReactNode | - |
| subTitle | custom subTitle text | ReactNode | - |
| backIcon | custom back icon, if false the back icon will not be displayed | ReactNode | `<Icon type="arrow-left" />` |
| tags | Tag list next to title | [Tag](https://ant.design/components/tag-cn/)[] \| [Tag](https://ant.design/components/tag-cn/) | - |
| extra | Operating area, at the end of the line of the title line | ReactNode | - |
| breadcrumb | breadcrumb config | [breadcrumb](https://ant.design/components/breadcrumb-cn/) | - |
| footer | PageHeader's footer, generally used to render TabBar | ReactNode | - |
| onBack | back icon click event | `()=>void` | `()=>history.back()` |

View File

@ -0,0 +1,102 @@
import * as React from 'react';
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
import Icon from '../icon';
import classnames from 'classnames';
import { BreadcrumbProps } from '../breadcrumb';
import { Divider, Breadcrumb } from '../index';
import Tag from '../tag';
import Wave from '../_util/wave';
export interface PageHeaderProps {
backIcon?: React.ReactNode;
prefixCls?: string;
title: React.ReactNode;
subTitle?: React.ReactNode;
style?: React.CSSProperties;
breadcrumb?: BreadcrumbProps;
tags?: Tag[];
footer?: React.ReactNode;
extra?: React.ReactNode;
onBack?: (e: React.MouseEvent<HTMLElement>) => void;
}
const renderBack = (
prefixCls: string,
backIcon?: React.ReactNode,
onBack?: (e: React.MouseEvent<HTMLElement>) => void,
) => {
if (!backIcon || !onBack) {
return null;
}
return (
<div
className={`${prefixCls}-back-icon`}
onClick={e => {
if (onBack) {
onBack(e);
}
}}
>
<Wave>{backIcon}</Wave>
<Divider type="vertical" />
</div>
);
};
const renderBreadcrumb = (breadcrumb: BreadcrumbProps) => {
return <Breadcrumb {...breadcrumb} />;
};
const renderHeader = (prefixCls: string, props: PageHeaderProps) => {
const { breadcrumb, backIcon, onBack } = props;
if (breadcrumb && breadcrumb.routes && breadcrumb.routes.length > 2) {
return renderBreadcrumb(breadcrumb);
}
return renderBack(prefixCls, backIcon, onBack);
};
const renderTitle = (prefixCls: string, props: PageHeaderProps) => {
const { title, subTitle, tags, extra } = props;
const titlePrefixCls = `${prefixCls}-title-view`;
return (
<div className={`${prefixCls}-title-view`}>
<span className={`${titlePrefixCls}-title`}>{title}</span>
{subTitle && <span className={`${titlePrefixCls}-sub-title`}>{subTitle}</span>}
{tags && <span className={`${titlePrefixCls}-tags`}>{tags}</span>}
{extra && <span className={`${titlePrefixCls}-extra`}>{extra}</span>}
</div>
);
};
const renderFooter = (prefixCls: string, footer: React.ReactNode) => {
if (footer) {
return <div className={`${prefixCls}-footer`}>{footer}</div>;
}
return null;
};
const PageHeader: React.SFC<PageHeaderProps> = props => (
<ConfigConsumer>
{({ getPrefixCls }: ConfigConsumerProps) => {
const { prefixCls: customizePrefixCls, style, footer, children } = props;
const prefixCls = getPrefixCls('page-header', customizePrefixCls);
const className = classnames(prefixCls, {
[`${prefixCls}-has-footer`]: footer,
});
return (
<div className={className} style={style}>
{renderHeader(prefixCls, props)}
{renderTitle(prefixCls, props)}
{children && <div className={`${prefixCls}-content-view`}>{children}</div>}
{renderFooter(prefixCls, footer)}
</div>
);
}}
</ConfigConsumer>
);
PageHeader.defaultProps = {
backIcon: <Icon type="arrow-left" />,
};
export default PageHeader;

View File

@ -0,0 +1,27 @@
---
category: Components
type: 导航
title: PageHeader
cols: 1
subtitle: 页头
---
页头可用于声明页面主题、展示用户所关注的页面重要信息,以及承载与当前页相关的操作项(包含页面级操作,页面间导航等)
## 何时使用
当需要使用户快速理解当前页是什么以及方便用户使用页面功能时使用,通常也可被用作页面间导航。
## API
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| title | 自定义标题文字 | ReactNode | - |
| subTitle | 自定义的二级标题文字 | ReactNode | - |
| backIcon | 自定义 back icon ,如果为 false 不渲染 back icon | ReactNode | `<Icon type="arrow-left" />` |
| tags | title 旁的 tag 列表 | [Tag](https://ant.design/components/tag-cn/)[] \| [Tag](https://ant.design/components/tag-cn/) | - |
| extra | 操作区,位于 title 行的行尾 | ReactNode | - |
| breadcrumb | 面包屑的配置 | [breadcrumb](https://ant.design/components/breadcrumb-cn/) | - |
| footer | PageHeader 的页脚,一般用于渲染 TabBar | ReactNode | - |
| onBack | 返回按钮的点击事件 | `()=>void` | `()=>history.back()` |

View File

@ -0,0 +1,92 @@
@import '../../style/themes/default';
@import '../../style/mixins/index';
@pageheader-prefix-cls: ~'@{ant-prefix}-page-header';
.@{pageheader-prefix-cls} {
.reset-component;
position: relative;
padding: 16px 32px;
background: @component-background;
&.@{pageheader-prefix-cls}-has-footer {
padding: 20px 32px;
padding-bottom: 0;
}
&-back-icon {
display: inline-block;
padding: 4px 0;
font-size: 16px;
line-height: 100%;
cursor: pointer;
i:hover {
color: @primary-color;
}
}
.@{ant-prefix}-divider {
height: 14px;
margin: 0 12px;
margin-top: 3px;
vertical-align: top;
}
.@{ant-prefix}-breadcrumb {
margin-bottom: 12px;
}
&-title-view {
display: inline-block;
&-title {
display: inline-block;
padding-right: 12px;
color: @heading-color;
font-weight: bold;
font-size: 16px;
line-height: 1.4;
}
&-sub-title {
display: inline-block;
padding-right: 12px;
color: @text-color-secondary;
font-size: 14px;
line-height: 1.8;
}
&-tags {
display: inline-block;
vertical-align: top;
}
&-extra {
position: absolute;
top: 16px;
right: 32px;
> * {
margin-right: 8px;
}
> *:last-child {
margin-right: 0;
}
}
}
&-content-view {
padding-top: 12px;
}
&-footer {
margin: 0 -8px;
padding-top: 24px;
.@{ant-prefix}-tabs-bar {
margin-bottom: 1px;
border-bottom: 0;
.@{ant-prefix}-tabs-nav .@{ant-prefix}-tabs-tab {
padding: 12px 8px;
padding-top: 0;
}
}
}
}

View File

@ -0,0 +1,5 @@
import './index.less';
import '../../divider/style';
import '../../breadcrumb/style';
import '../../typography/style';

View File

@ -19,7 +19,7 @@ export interface PaginationProps {
showSizeChanger?: boolean;
pageSizeOptions?: string[];
onShowSizeChange?: (current: number, size: number) => void;
showQuickJumper?: boolean;
showQuickJumper?: boolean | { goButton?: React.ReactNode };
showTotal?: (total: number, range: [number, number]) => React.ReactNode;
size?: string;
simple?: boolean;

View File

@ -27,7 +27,7 @@ A long list can be divided into several pages by `Pagination`, and only one page
| itemRender | to customize item innerHTML | (page, type: 'page' \| 'prev' \| 'next', originalElement) => React.ReactNode | - |
| pageSize | number of data items per page | number | - |
| pageSizeOptions | specify the sizeChanger options | string\[] | \['10', '20', '30', '40'] |
| showQuickJumper | determine whether you can jump to pages directly | boolean | false |
| showQuickJumper | determine whether you can jump to pages directly | boolean \| `{ goButton: ReactNode }` | false |
| showSizeChanger | determine whether `pageSize` can be changed | boolean | false |
| showTotal | to display the total number and range | Function(total, range) | - |
| simple | whether to use simple mode | boolean | - |

View File

@ -28,7 +28,7 @@ cols: 1
| itemRender | 用于自定义页码的结构,可用于优化 SEO | (page, type: 'page' \| 'prev' \| 'next', originalElement) => React.ReactNode | - |
| pageSize | 每页条数 | number | - |
| pageSizeOptions | 指定每页可以显示多少条 | string\[] | \['10', '20', '30', '40'] |
| showQuickJumper | 是否可以快速跳转至某页 | boolean | false |
| showQuickJumper | 是否可以快速跳转至某页 | boolean \| `{ goButton: ReactNode }` | false |
| showSizeChanger | 是否可以改变 pageSize | boolean | false |
| showTotal | 用于显示数据总量和当前数据顺序 | Function(total, range) | - |
| simple | 当添加该属性时,显示为简单分页 | boolean | - |

View File

@ -247,7 +247,10 @@ exports[`renders ./components/progress/demo/circle-dynamic.md correctly 1`] = `
width="1em"
>
<path
d="M848 474H550V152h-76v322H176c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h298v322h76V550h298c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z"
d="M482 152h60q8 0 8 8v704q0 8-8 8h-60q-8 0-8-8V160q0-8 8-8z"
/>
<path
d="M176 474h672q8 0 8 8v60q0 8-8 8H176q-8 0-8-8v-60q0-8 8-8z"
/>
</svg>
</i>
@ -530,7 +533,10 @@ exports[`renders ./components/progress/demo/dynamic.md correctly 1`] = `
width="1em"
>
<path
d="M848 474H550V152h-76v322H176c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h298v322h76V550h298c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z"
d="M482 152h60q8 0 8 8v704q0 8-8 8h-60q-8 0-8-8V160q0-8 8-8z"
/>
<path
d="M176 474h672q8 0 8 8v60q0 8-8 8H176q-8 0-8-8v-60q0-8 8-8z"
/>
</svg>
</i>

View File

@ -103,8 +103,8 @@ export default class Progress extends React.Component<ProgressProps, {}> {
} = props;
const prefixCls = getPrefixCls('progress', customizePrefixCls);
const progressStatus =
parseInt(successPercent !== undefined ? successPercent.toString() : percent.toString(), 10) >= 100 &&
!('status' in props)
parseInt(successPercent !== undefined ? successPercent.toString() : percent.toString(), 10) >=
100 && !('status' in props)
? 'success'
: status || 'normal';
let progress;

View File

@ -147,7 +147,7 @@ exports[`renders ./components/select/demo/basic.md correctly 1`] = `
</div>
</div>
<div
class="ant-select ant-select-enabled"
class="ant-select ant-select-enabled ant-select-loading"
style="width:120px"
>
<div

View File

@ -204,6 +204,7 @@ export default class Select<T = SelectValue> extends React.Component<SelectProps
removeIcon,
clearIcon,
menuItemSelectedIcon,
showArrow,
...restProps
} = this.props;
const rest = omit(restProps, ['inputIcon']);
@ -213,6 +214,7 @@ export default class Select<T = SelectValue> extends React.Component<SelectProps
{
[`${prefixCls}-lg`]: size === 'large',
[`${prefixCls}-sm`]: size === 'small',
[`${prefixCls}-show-arrow`]: showArrow,
},
className,
);
@ -261,6 +263,7 @@ export default class Select<T = SelectValue> extends React.Component<SelectProps
removeIcon={finalRemoveIcon}
clearIcon={finalClearIcon}
menuItemSelectedIcon={finalMenuItemSelectedIcon}
showArrow={showArrow}
{...rest}
{...modeConfig}
prefixCls={prefixCls}

View File

@ -182,7 +182,8 @@
line-height: @input-height-lg - 8px;
}
}
.@{select-prefix-cls}-selection__clear {
.@{select-prefix-cls}-selection__clear,
.@{select-prefix-cls}-arrow {
top: @input-height-lg / 2;
}
}
@ -204,7 +205,8 @@
line-height: @input-height-sm - 10px;
}
}
.@{select-prefix-cls}-selection__clear {
.@{select-prefix-cls}-selection__clear,
.@{select-prefix-cls}-arrow {
top: @input-height-sm / 2;
}
}
@ -364,7 +366,8 @@
}
}
.@{select-prefix-cls}-selection__clear {
.@{select-prefix-cls}-selection__clear,
.@{select-prefix-cls}-arrow {
top: @input-height-base / 2;
}
}
@ -373,7 +376,8 @@
padding-right: 16px;
}
&-allow-clear &-selection--multiple &-selection__rendered {
&-allow-clear &-selection--multiple &-selection__rendered,
&-show-arrow &-selection--multiple &-selection__rendered {
margin-right: 20px; // In case that clear button will overlap content
}
@ -410,7 +414,8 @@
transition: all 0.3s @ease-in-out, height 0s;
}
}
&-combobox&-allow-clear &-selection:hover &-selection__rendered {
&-combobox&-allow-clear &-selection:hover &-selection__rendered,
&-combobox&-show-arrow &-selection:hover &-selection__rendered {
margin-right: 20px; // In case that clear button will overlap content
}
}
@ -556,7 +561,7 @@
font-size: 12px;
text-shadow: 0 0.1px 0, 0.1px 0 0, 0 -0.1px 0, -0.1px 0;
transform: translateY(-50%);
transition: all .2s;
transition: all 0.2s;
}
&:hover .@{select-prefix-cls}-selected-icon {

View File

@ -10,6 +10,11 @@ const REFRESH_INTERVAL = 1000 / 30;
interface CountdownProps extends StatisticProps {
value?: countdownValueType;
format?: string;
onFinish?: () => void;
}
function getTime(value?: countdownValueType) {
return interopDefault(moment)(value).valueOf();
}
class Countdown extends React.Component<CountdownProps, {}> {
@ -34,7 +39,7 @@ class Countdown extends React.Component<CountdownProps, {}> {
syncTimer = () => {
const { value } = this.props;
const timestamp = interopDefault(moment)(value).valueOf();
const timestamp = getTime(value);
if (timestamp >= Date.now()) {
this.startTimer();
} else {
@ -43,7 +48,7 @@ class Countdown extends React.Component<CountdownProps, {}> {
};
startTimer = () => {
if (this.countdownId !== undefined) return;
if (this.countdownId) return;
this.countdownId = window.setInterval(() => {
this.forceUpdate();
@ -51,8 +56,16 @@ class Countdown extends React.Component<CountdownProps, {}> {
};
stopTimer = () => {
const { onFinish, value } = this.props;
if (this.countdownId) {
clearInterval(this.countdownId);
this.countdownId = undefined;
const timestamp = getTime(value);
if (onFinish && timestamp < Date.now()) {
onFinish();
}
}
};
formatCountdown = (value: countdownValueType, config: FormatConfig) => {

View File

@ -61,17 +61,47 @@ describe('Statistic', () => {
it('time going', async () => {
const now = Date.now() + 1000;
const wrapper = mount(<Statistic.Countdown value={now} />);
const onFinish = jest.fn();
const wrapper = mount(<Statistic.Countdown value={now} onFinish={onFinish} />);
wrapper.update();
// setInterval should work
const instance = wrapper.instance();
expect(instance.countdownId).not.toBe(undefined);
await delay(50);
await delay(10);
wrapper.unmount();
expect(instance.countdownId).toBe(undefined);
expect(onFinish).not.toBeCalled();
});
describe('time finished', () => {
it('not call if time already passed', () => {
const now = Date.now() - 1000;
const onFinish = jest.fn();
const wrapper = mount(<Statistic.Countdown value={now} onFinish={onFinish} />);
wrapper.update();
const instance = wrapper.instance();
expect(instance.countdownId).toBe(undefined);
expect(onFinish).not.toBeCalled();
});
it('called if finished', async () => {
jest.useFakeTimers();
const now = Date.now() + 10;
const onFinish = jest.fn();
const wrapper = mount(<Statistic.Countdown value={now} onFinish={onFinish} />);
wrapper.update();
MockDate.set(moment('2019-11-28 00:00:00'));
jest.runAllTimers();
expect(onFinish).toBeCalled();
jest.useFakeTimers();
});
});
});
});

View File

@ -19,10 +19,14 @@ import { Statistic, Row, Col } from 'antd';
const Countdown = Statistic.Countdown;
const deadline = Date.now() + 1000 * 60 * 60 * 24 * 2 + 1000 * 30; // Moment is also OK
function onFinish() {
console.log('finished!');
}
ReactDOM.render(
<Row gutter={16}>
<Col span={12}>
<Countdown title="Countdown" value={deadline} />
<Countdown title="Countdown" value={deadline} onFinish={onFinish} />
</Col>
<Col span={12}>
<Countdown title="Million Seconds" value={deadline} format="HH:mm:ss:SSS" />

View File

@ -32,6 +32,7 @@ Display statistic number.
| Property | Description | Type | Default |
| -------- | ----------- | ---- | ------- |
| format | Format as [moment](http://momentjs.com/) | string | 'HH:mm:ss' |
| onFinish | Trigger when time's up | () => void | - |
| prefix | prefix node of value | string \| ReactNode | - |
| suffix | suffix node of value | string \| ReactNode | - |
| title | Display title | string \| ReactNode | - |

View File

@ -33,6 +33,7 @@ title: Statistic
| 参数 | 说明 | 类型 | 默认值 |
| -------- | ----------- | ---- | ------- |
| format | 格式化倒计时展示,参考 [moment](http://momentjs.com/) | string | 'HH:mm:ss' |
| onFinish | 倒计时完成时触发 | () => void | - |
| prefix | 设置数值的前缀 | string \| ReactNode | - |
| suffix | 设置数值的后缀 | string \| ReactNode | - |
| title | 数值的标题 | string \| ReactNode | - |

View File

@ -38,15 +38,17 @@
@body-background: #fff;
// Base background color for most components
@component-background: #fff;
@font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC',
'Hiragino Sans GB', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif,
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
@font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB',
'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif, 'Apple Color Emoji',
'Segoe UI Emoji', 'Segoe UI Symbol';
@code-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;
@heading-color: fade(@black, 85%);
@text-color: fade(@black, 65%);
@text-color-secondary: fade(@black, 45%);
@text-color-warning: @gold-7;
@text-color-danger: @red-7;
@text-color-inverse: @white;
@icon-color-hover: fade(@black, 75%);
@heading-color: fade(#000, 85%);
@heading-color-dark: fade(@white, 100%);
@text-color-dark: fade(@white, 85%);
@text-color-secondary-dark: fade(@white, 65%);
@ -54,6 +56,10 @@
@font-size-base: 14px;
@font-size-lg: @font-size-base + 2px;
@font-size-sm: 12px;
@heading-1-size: ceil(@font-size-base * 2.71);
@heading-2-size: ceil(@font-size-base * 2.14);
@heading-3-size: ceil(@font-size-base * 1.71);
@heading-4-size: ceil(@font-size-base * 1.42);
@line-height-base: 1.5;
@border-radius-base: 4px;
@border-radius-sm: 2px;

View File

@ -7,7 +7,11 @@ import { PaginationConfig } from '../pagination';
export { PaginationConfig } from '../pagination';
export type CompareFn<T> = (a: T, b: T, sortOrder?: SortOrder) => number;
export type ColumnFilterItem = { text: string; value: string; children?: ColumnFilterItem[] };
export type ColumnFilterItem = {
text: React.ReactNode;
value: string;
children?: ColumnFilterItem[];
};
export interface ColumnProps<T> {
title?:

View File

@ -1141,7 +1141,10 @@ exports[`renders ./components/tabs/demo/editable-card.md correctly 1`] = `
width="1em"
>
<path
d="M848 474H550V152h-76v322H176c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h298v322h76V550h298c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z"
d="M482 152h60q8 0 8 8v704q0 8-8 8h-60q-8 0-8-8V160q0-8 8-8z"
/>
<path
d="M176 474h672q8 0 8 8v60q0 8-8 8H176q-8 0-8-8v-60q0-8 8-8z"
/>
</svg>
</i>

View File

@ -258,7 +258,10 @@ exports[`renders ./components/tag/demo/control.md correctly 1`] = `
width="1em"
>
<path
d="M848 474H550V152h-76v322H176c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h298v322h76V550h298c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z"
d="M482 152h60q8 0 8 8v704q0 8-8 8h-60q-8 0-8-8V160q0-8 8-8z"
/>
<path
d="M176 474h672q8 0 8 8v60q0 8-8 8H176q-8 0-8-8v-60q0-8 8-8z"
/>
</svg>
</i>

View File

@ -46,6 +46,7 @@ import moment from 'moment';
| popupStyle | style of panel | object | - |
| secondStep | interval between seconds in picker | number | 1 |
| suffixIcon | The custom suffix icon | ReactNode | - |
| clearIcon | The custom clear icon | ReactNode | - |
| use12Hours | display as 12 hours format, with default format `h:mm:ss a` | boolean | false |
| value | to set time | [moment](http://momentjs.com/) | - |
| onChange | a callback function, can be executed when the selected time is changing | function(time: moment, timeString: string): void | - |

View File

@ -53,6 +53,7 @@ export interface TimePickerProps {
popupClassName?: string;
popupStyle?: React.CSSProperties;
suffixIcon?: React.ReactNode;
clearIcon?: React.ReactNode;
}
export interface TimePickerLocale {
@ -165,11 +166,17 @@ class TimePicker extends React.Component<TimePickerProps, any> {
}
renderClearIcon(prefixCls: string) {
const {} = this.props;
const { clearIcon } = this.props;
const clearIcon = <Icon type="close-circle" className={`${prefixCls}-clear`} theme="filled" />;
const clearIconPrefixCls = `${prefixCls}-clear`;
return clearIcon;
if (clearIcon && React.isValidElement<{ className?: string }>(clearIcon)) {
return React.cloneElement(clearIcon, {
className: classNames(clearIcon.props.className, clearIconPrefixCls),
});
}
return <Icon type="close-circle" className={clearIconPrefixCls} theme="filled" />;
}
renderTimePicker = (locale: TimePickerLocale) => (

View File

@ -47,6 +47,7 @@ import moment from 'moment';
| popupStyle | 弹出层样式对象 | object | - |
| secondStep | 秒选项间隔 | number | 1 |
| suffixIcon | 自定义的选择框后缀图标 | ReactNode | - |
| clearIcon | 自定义的清除图标 | ReactNode | - |
| use12Hours | 使用 12 小时制,为 true 时 `format` 默认为 `h:mm:ss a` | boolean | false |
| value | 当前时间 | [moment](http://momentjs.com/) | 无 |
| onChange | 时间发生变化的回调 | function(time: moment, timeString: string): void | 无 |

View File

@ -160,8 +160,12 @@
.@{select-prefix-cls}-tree-dropdown {
.reset-component;
.@{select-prefix-cls}-dropdown-search {
position: sticky;
top: 0;
z-index: 1;
display: block;
padding: 4px;
background: @component-background;
.@{select-prefix-cls}-search__field__wrap {
width: 100%;
}

View File

@ -0,0 +1,483 @@
import * as React from 'react';
import classNames from 'classnames';
import { polyfill } from 'react-lifecycles-compat';
import toArray from 'rc-util/lib/Children/toArray';
import copy from 'copy-to-clipboard';
import omit from 'omit.js';
import { withConfigConsumer, ConfigConsumerProps, configConsumerProps } from '../config-provider';
import LocaleReceiver from '../locale-provider/LocaleReceiver';
import warning from '../_util/warning';
import TransButton from '../_util/transButton';
import ResizeObserver from '../_util/resizeObserver';
import raf from '../_util/raf';
import isStyleSupport from '../_util/styleChecker';
import Icon from '../icon';
import Tooltip from '../tooltip';
import Typography, { TypographyProps } from './Typography';
import Editable from './Editable';
import { measure } from './util';
export type BaseType = 'secondary' | 'danger' | 'warning';
const isLineClampSupport = isStyleSupport('webkitLineClamp');
const isTextOverflowSupport = isStyleSupport('textOverflow');
interface CopyConfig {
text?: string;
onCopy?: () => void;
}
interface EditConfig {
editing?: boolean;
onStart?: () => void;
onChange?: (value: string) => void;
}
interface EllipsisConfig {
rows?: number;
expandable?: boolean;
onExpand?: () => void;
}
export interface BlockProps extends TypographyProps {
editable?: boolean | EditConfig;
copyable?: boolean | CopyConfig;
type?: BaseType;
disabled?: boolean;
ellipsis?: boolean | EllipsisConfig;
// decorations
code?: boolean;
mark?: boolean;
underline?: boolean;
delete?: boolean;
strong?: boolean;
}
function wrapperDecorations(
{ mark, code, underline, delete: del, strong }: BlockProps,
content: React.ReactNode,
) {
let currentContent = content;
function wrap(needed: boolean | undefined, tag: string) {
if (!needed) return;
currentContent = React.createElement(tag, {
children: currentContent,
});
}
wrap(strong, 'strong');
wrap(underline, 'u');
wrap(del, 'del');
wrap(code, 'code');
wrap(mark, 'mark');
return currentContent;
}
interface InternalBlockProps extends BlockProps {
component: string;
}
interface BaseState {
edit: boolean;
copied: boolean;
ellipsisText: string;
ellipsisContent: React.ReactNode;
isEllipsis: boolean;
expanded: boolean;
clientRendered: boolean;
}
interface Locale {
edit?: string;
copy?: string;
copied?: string;
expand?: string;
}
const ELLIPSIS_STR = '...';
class Base extends React.Component<InternalBlockProps & ConfigConsumerProps, BaseState> {
static defaultProps = {
children: '',
};
static getDerivedStateFromProps(nextProps: BlockProps) {
const { children, editable } = nextProps;
warning(
!editable || typeof children === 'string',
'Typography',
'When `editable` is enabled, the `children` should use string.',
);
return {};
}
editIcon?: TransButton;
content?: HTMLElement;
copyId?: number;
rafId?: number;
// Locale
expandStr?: string;
copyStr?: string;
copiedStr?: string;
editStr?: string;
state: BaseState = {
edit: false,
copied: false,
ellipsisText: '',
ellipsisContent: null,
isEllipsis: false,
expanded: false,
clientRendered: false,
};
componentDidMount() {
this.setState({ clientRendered: true });
this.resizeOnNextFrame();
}
componentDidUpdate(prevProps: BlockProps) {
const ellipsis = this.getEllipsis();
const prevEllipsis = this.getEllipsis(prevProps);
if (this.props.children !== prevProps.children || ellipsis.rows !== prevEllipsis.rows) {
this.resizeOnNextFrame();
}
}
componentWillUnmount() {
window.clearTimeout(this.copyId);
raf.cancel(this.rafId);
}
// =============== Expend ===============
onExpandClick = () => {
const { onExpand } = this.getEllipsis();
this.setState({ expanded: true });
if (onExpand) {
onExpand();
}
};
// ================ Edit ================
onEditClick = () => {
this.triggerEdit(true);
};
onEditChange = (value: string) => {
const { onChange } = this.getEditable();
if (onChange) {
onChange(value);
}
this.triggerEdit(false);
};
onEditCancel = () => {
this.triggerEdit(false);
};
// ================ Copy ================
onCopyClick = () => {
const { children, copyable } = this.props;
const copyConfig: CopyConfig = {
...(typeof copyable === 'object' ? copyable : null),
};
if (copyConfig.text === undefined) {
copyConfig.text = String(children);
}
copy(copyConfig.text || '');
this.setState({ copied: true }, () => {
if (copyConfig.onCopy) {
copyConfig.onCopy();
}
this.copyId = window.setTimeout(() => {
this.setState({ copied: false });
}, 3000);
});
};
getEditable(props?: BlockProps): EditConfig {
const { edit } = this.state;
const { editable } = props || this.props;
if (!editable) return { editing: edit };
return {
editing: edit,
...(typeof editable === 'object' ? editable : null),
};
}
getEllipsis(props?: BlockProps): EllipsisConfig {
const { ellipsis } = props || this.props;
if (!ellipsis) return {};
return {
rows: 1,
expandable: false,
...(typeof ellipsis === 'object' ? ellipsis : null),
};
}
setContentRef = (node: HTMLElement) => {
this.content = node;
};
setEditRef = (node: TransButton) => {
this.editIcon = node;
};
triggerEdit = (edit: boolean) => {
const { onStart } = this.getEditable();
if (edit && onStart) {
onStart();
}
this.setState({ edit }, () => {
if (!edit && this.editIcon) {
this.editIcon.focus();
}
});
};
// ============== Ellipsis ==============
resizeOnNextFrame = () => {
raf.cancel(this.rafId);
this.rafId = raf(() => {
// Do not bind `syncEllipsis`. It need for test usage on prototype
this.syncEllipsis();
});
};
canUseCSSEllipsis(): boolean {
const { clientRendered } = this.state;
const { editable, copyable } = this.props;
const { rows, expandable } = this.getEllipsis();
// Can't use css ellipsis since we need to provide the place for button
if (editable || copyable || expandable || !clientRendered) {
return false;
}
if (rows === 1) {
return isTextOverflowSupport;
}
return isLineClampSupport;
}
syncEllipsis() {
const { ellipsisText, isEllipsis, expanded } = this.state;
const { rows } = this.getEllipsis();
const { children } = this.props;
if (!rows || rows < 0 || !this.content || expanded) return;
// Do not measure if css already support ellipsis
if (this.canUseCSSEllipsis()) return;
warning(
toArray(children).every((child: React.ReactNode) => typeof child === 'string'),
'Typography',
'`ellipsis` should use string as children only.',
);
const { content, text, ellipsis } = measure(
this.content,
rows,
children,
this.renderOperations(true),
ELLIPSIS_STR,
);
if (ellipsisText !== text || isEllipsis !== ellipsis) {
this.setState({ ellipsisText: text, ellipsisContent: content, isEllipsis: ellipsis });
}
}
renderExpand(forceRender?: boolean) {
const { expandable } = this.getEllipsis();
const { prefixCls } = this.props;
const { expanded, isEllipsis } = this.state;
if (!expandable) return null;
// force render expand icon for measure usage or it will cause dead loop
if (!forceRender && (expanded || !isEllipsis)) return null;
return (
<a
key="expand"
className={`${prefixCls}-expand`}
onClick={this.onExpandClick}
aria-label={this.expandStr}
>
{this.expandStr}
</a>
);
}
renderEdit() {
const { editable, prefixCls } = this.props;
if (!editable) return;
return (
<Tooltip key="edit" title={this.editStr}>
<TransButton
ref={this.setEditRef}
className={`${prefixCls}-edit`}
onClick={this.onEditClick}
aria-label={this.editStr}
>
<Icon role="button" type="edit" />
</TransButton>
</Tooltip>
);
}
renderCopy() {
const { copied } = this.state;
const { copyable, prefixCls } = this.props;
if (!copyable) return;
const title = copied ? this.copiedStr : this.copyStr;
return (
<Tooltip key="copy" title={title}>
<TransButton
className={classNames(`${prefixCls}-copy`, copied && `${prefixCls}-copy-success`)}
onClick={this.onCopyClick}
aria-label={title}
>
<Icon role="button" type={copied ? 'check' : 'copy'} />
</TransButton>
</Tooltip>
);
}
renderEditInput() {
const { children, prefixCls } = this.props;
return (
<Editable
value={typeof children === 'string' ? children : ''}
onSave={this.onEditChange}
onCancel={this.onEditCancel}
prefixCls={prefixCls}
/>
);
}
renderOperations(forceRenderExpanded?: boolean) {
return [this.renderExpand(forceRenderExpanded), this.renderEdit(), this.renderCopy()].filter(
node => node,
);
}
renderContent() {
const { ellipsisContent, isEllipsis, expanded } = this.state;
const {
component,
children,
className,
prefixCls,
type,
disabled,
style,
...restProps
} = this.props;
const { rows } = this.getEllipsis();
const textProps = omit(restProps, [
'prefixCls',
'editable',
'copyable',
'ellipsis',
'mark',
'underline',
'mark',
'code',
'delete',
'underline',
'strong',
...configConsumerProps,
]);
const cssEllipsis = this.canUseCSSEllipsis();
const cssTextOverflow = rows === 1 && cssEllipsis;
const cssLineClamp = rows && rows > 1 && cssEllipsis;
let textNode: React.ReactNode = children;
let ariaLabel: string | null = null;
// Only use js ellipsis when css ellipsis not support
if (rows && isEllipsis && !expanded && !cssEllipsis) {
ariaLabel = String(children);
// We move full content to outer element to avoid repeat read the content by accessibility
textNode = (
<span title={String(children)} aria-hidden="true">
{ellipsisContent}
{ELLIPSIS_STR}
</span>
);
}
textNode = wrapperDecorations(this.props, textNode);
return (
<LocaleReceiver componentName="Text">
{({ edit, copy: copyStr, copied, expand }: Locale) => {
this.editStr = edit;
this.copyStr = copyStr;
this.copiedStr = copied;
this.expandStr = expand;
return (
<ResizeObserver onResize={this.resizeOnNextFrame} disabled={!rows}>
<Typography
className={classNames(className, {
[`${prefixCls}-${type}`]: type,
[`${prefixCls}-disabled`]: disabled,
[`${prefixCls}-ellipsis`]: rows,
[`${prefixCls}-ellipsis-single-line`]: cssTextOverflow,
[`${prefixCls}-ellipsis-multiple-line`]: cssLineClamp,
})}
style={{
...style,
WebkitLineClamp: cssLineClamp ? rows : null,
}}
component={component}
setContentRef={this.setContentRef}
aria-label={ariaLabel}
{...textProps}
>
{textNode}
{this.renderOperations()}
</Typography>
</ResizeObserver>
);
}}
</LocaleReceiver>
);
}
render() {
const { editing } = this.getEditable();
if (editing) {
return this.renderEditInput();
}
return this.renderContent();
}
}
polyfill(Base);
export default withConfigConsumer<InternalBlockProps>({
prefixCls: 'typography',
})(Base);

View File

@ -0,0 +1,133 @@
import * as React from 'react';
import KeyCode from 'rc-util/lib/KeyCode';
import { polyfill } from 'react-lifecycles-compat';
import Icon from '../icon';
import TextArea from '../input/TextArea';
interface EditableProps {
prefixCls?: string;
value?: string;
['aria-label']?: string;
onSave: (value: string) => void;
onCancel: () => void;
}
interface EditableState {
current: string;
prevValue?: string;
}
class Editable extends React.Component<EditableProps, EditableState> {
static getDerivedStateFromProps(nextProps: EditableProps, prevState: EditableState) {
const { prevValue } = prevState;
const { value } = nextProps;
const newState: Partial<EditableState> = {
prevValue: value,
};
if (prevValue !== value) {
newState.current = value;
}
return newState;
}
textarea?: TextArea;
lastKeyCode?: number;
inComposition?: boolean = false;
state = {
current: '',
};
componentDidMount() {
if (this.textarea) {
this.textarea.focus();
}
}
onChange: React.ChangeEventHandler<HTMLTextAreaElement> = ({ target: { value } }) => {
this.setState({ current: value.replace(/[\r\n]/g, '') });
};
onCompositionStart = () => {
this.inComposition = true;
};
onCompositionEnd = () => {
this.inComposition = false;
};
onKeyDown: React.KeyboardEventHandler<HTMLTextAreaElement> = ({ keyCode }) => {
// We don't record keyCode when IME is using
if (this.inComposition) return;
this.lastKeyCode = keyCode;
};
onKeyUp: React.KeyboardEventHandler<HTMLTextAreaElement> = ({
keyCode,
ctrlKey,
altKey,
metaKey,
shiftKey,
}) => {
const { onCancel } = this.props;
// Check if it's a real key
if (
this.lastKeyCode === keyCode &&
!this.inComposition &&
!ctrlKey &&
!altKey &&
!metaKey &&
!shiftKey
) {
if (keyCode === KeyCode.ENTER) {
this.confirmChange();
} else if (keyCode === KeyCode.ESC) {
onCancel();
}
}
};
onBlur: React.FocusEventHandler<HTMLTextAreaElement> = () => {
this.confirmChange();
};
confirmChange = () => {
const { current } = this.state;
const { onSave } = this.props;
onSave(current.trim());
};
setTextarea = (textarea: TextArea) => {
this.textarea = textarea;
};
render() {
const { current } = this.state;
const { prefixCls, ['aria-label']: ariaLabel } = this.props;
return (
<div className={`${prefixCls}-edit-content`}>
<TextArea
ref={this.setTextarea}
value={current}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
onKeyUp={this.onKeyUp}
onCompositionStart={this.onCompositionStart}
onCompositionEnd={this.onCompositionEnd}
onBlur={this.onBlur}
aria-label={ariaLabel}
autosize
/>
<Icon type="enter" className={`${prefixCls}-edit-content-confirm`} />
</div>
);
}
}
polyfill(Editable);
export default Editable;

View File

@ -0,0 +1,8 @@
import * as React from 'react';
import Base, { BlockProps } from './Base';
interface ParagraphProps extends BlockProps {}
const Paragraph: React.SFC<ParagraphProps> = props => <Base {...props} component="div" />;
export default Paragraph;

View File

@ -0,0 +1,18 @@
import * as React from 'react';
import warning from '../_util/warning';
import Base, { BlockProps } from './Base';
interface TextProps extends BlockProps {
ellipsis: boolean;
}
const Text: React.SFC<TextProps> = ({ ellipsis, ...restProps }) => {
warning(
typeof ellipsis !== 'object',
'Typography.Text',
'`ellipsis` is only support boolean value.',
);
return <Base {...restProps} ellipsis={!!ellipsis} component="span" />;
};
export default Text;

View File

@ -0,0 +1,24 @@
import * as React from 'react';
import warning from 'warning';
import Base, { BlockProps } from './Base';
import { tupleNum, Omit } from '../_util/type';
const TITLE_ELE_LIST = tupleNum(1, 2, 3, 4);
type TitleProps = Omit<BlockProps & { level?: (typeof TITLE_ELE_LIST)[number] }, 'strong'>;
const Title: React.SFC<TitleProps> = props => {
const { level = 1, ...restProps } = props;
let component: string;
if (TITLE_ELE_LIST.indexOf(level) !== -1) {
component = `h${level}`;
} else {
warning(false, 'Title only accept `1 | 2 | 3 | 4` as `level` value.');
component = 'h1';
}
return <Base {...restProps} component={component} />;
};
export default Title;

View File

@ -0,0 +1,47 @@
import * as React from 'react';
import classNames from 'classnames';
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
export interface TypographyProps {
id?: string;
prefixCls?: string;
className?: string;
style?: React.CSSProperties;
children?: React.ReactNode;
['aria-label']?: string;
}
interface InternalTypographyProps extends TypographyProps {
component?: string;
setContentRef: (node: HTMLElement) => void;
}
const Typography: React.SFC<InternalTypographyProps> = ({
prefixCls: customizePrefixCls,
component = 'article',
className,
['aria-label']: ariaLabel,
setContentRef,
children,
...restProps
}) => (
<ConfigConsumer>
{({ getPrefixCls }: ConfigConsumerProps) => {
const Component = component as any;
const prefixCls = getPrefixCls('typography', customizePrefixCls);
return (
<Component
className={classNames(prefixCls, className)}
aria-label={ariaLabel}
ref={setContentRef}
{...restProps}
>
{children}
</Component>
);
}}
</ConfigConsumer>
);
export default Typography;

View File

@ -0,0 +1,638 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders ./components/typography/demo/basic.md correctly 1`] = `
<article
class="ant-typography"
>
<h1
class="ant-typography"
>
Introduction
</h1>
<div
class="ant-typography"
>
In the process of internal desktop applications development, many different design specs and implementations would be involved, which might cause designers and developers difficulties and duplication and reduce the efficiency of development.
</div>
<div
class="ant-typography"
>
After massive project practice and summaries, Ant Design, a design language for background applications, is refined by Ant UED Team, which aims to
<span
class="ant-typography"
>
<strong>
uniform the user interface specs for internal background projects, lower the unnecessary cost of design differences and implementation and liberate the resources of design and front-end development
</strong>
</span>
.
</div>
<h2
class="ant-typography"
>
Guidelines and Resources
</h2>
<div
class="ant-typography"
>
We supply a series of design principles, practical patterns and high quality design resources (
<span
class="ant-typography"
>
<code>
Sketch
</code>
</span>
and
<span
class="ant-typography"
>
<code>
Axure
</code>
</span>
), to help people create their product prototypes beautifully and efficiently.
</div>
<div
class="ant-typography"
>
<ul>
<li>
<a
href="/docs/spec/proximity"
>
Principles
</a>
</li>
<li>
<a
href="/docs/pattern/navigation"
>
Patterns
</a>
</li>
<li>
<a
href="/docs/resource/download"
>
Resource Download
</a>
</li>
</ul>
</div>
<div
class="ant-divider ant-divider-horizontal"
/>
<h1
class="ant-typography"
>
介绍
</h1>
<div
class="ant-typography"
>
蚂蚁的企业级产品是一个庞大且复杂的体系。这类产品不仅量级巨大且功能复杂,而且变动和并发频繁,常常需要设计与开发能够快速的做出响应。同时这类产品中有存在很多类似的页面以及组件,可以通过抽象得到一些稳定且高复用性的内容。
</div>
<div
class="ant-typography"
>
随着商业化的趋势,越来越多的企业级产品对更好的用户体验有了进一步的要求。带着这样的一个终极目标,我们(蚂蚁金服体验技术部)经过大量的项目实践和总结,逐步打磨出一个服务于企业级产品的设计体系 Ant Design。基于
<span
class="ant-typography"
>
<mark>
『确定』和『自然』
</mark>
</span>
的设计价值观,通过模块化的解决方案,降低冗余的生产成本,让设计者专注于
<span
class="ant-typography"
>
<strong>
更好的用户体验
</strong>
</span>
</div>
<h2
class="ant-typography"
>
设计资源
</h2>
<div
class="ant-typography"
>
我们提供完善的设计原则、最佳实践和设计资源文件(
<span
class="ant-typography"
>
<code>
Sketch
</code>
</span>
<span
class="ant-typography"
>
<code>
Axure
</code>
</span>
),来帮助业务快速设计出高质量的产品原型。
</div>
<div
class="ant-typography"
>
<ul>
<li>
<a
href="/docs/spec/proximity"
>
设计原则
</a>
</li>
<li>
<a
href="/docs/pattern/navigation"
>
设计模式
</a>
</li>
<li>
<a
href="/docs/resource/download"
>
设计资源
</a>
</li>
</ul>
</div>
</article>
`;
exports[`renders ./components/typography/demo/ellipsis.md correctly 1`] = `
<div>
<div
class="ant-typography ant-typography-ellipsis"
>
Ant Design, a design language for background applications, is refined by Ant UED Team. Ant Design, a design language for background applications, is refined by Ant UED Team. Ant Design, a design language for background applications, is refined by Ant UED Team. Ant Design, a design language for background applications, is refined by Ant UED Team. Ant Design, a design language for background applications, is refined by Ant UED Team. Ant Design, a design language for background applications, is refined by Ant UED Team.
</div>
<div
class="ant-typography ant-typography-ellipsis"
>
Ant Design, a design language for background applications, is refined by Ant UED Team. Ant Design, a design language for background applications, is refined by Ant UED Team. Ant Design, a design language for background applications, is refined by Ant UED Team. Ant Design, a design language for background applications, is refined by Ant UED Team. Ant Design, a design language for background applications, is refined by Ant UED Team. Ant Design, a design language for background applications, is refined by Ant UED Team.
</div>
</div>
`;
exports[`renders ./components/typography/demo/ellipsis-debug.md correctly 1`] = `
<div>
<button
aria-checked="true"
checked=""
class="ant-switch ant-switch-checked"
role="switch"
type="button"
>
<span
class="ant-switch-inner"
>
Long Text
</span>
</button>
<button
aria-checked="false"
class="ant-switch"
role="switch"
type="button"
>
<span
class="ant-switch-inner"
/>
</button>
<button
aria-checked="false"
class="ant-switch"
role="switch"
type="button"
>
<span
class="ant-switch-inner"
/>
</button>
<button
aria-checked="false"
class="ant-switch"
role="switch"
type="button"
>
<span
class="ant-switch-inner"
/>
</button>
<div
class="ant-slider"
>
<div
class="ant-slider-rail"
/>
<div
class="ant-slider-track"
style="left:0%;width:0%"
/>
<div
class="ant-slider-step"
/>
<div
aria-disabled="false"
aria-valuemax="10"
aria-valuemin="1"
aria-valuenow="1"
class="ant-slider-handle"
role="slider"
style="left:0%"
tabindex="0"
/>
<div
class="ant-slider-mark"
/>
</div>
<div
class="ant-typography ant-typography-ellipsis"
>
Ant Design, a design language for background applications, is refined by Ant UED Team. This is a nest sample
<span
class="ant-typography"
>
<code>
<del>
<strong>
Test
</strong>
</del>
</code>
</span>
case.Bnt Design, a design language for background applications, is refined by Ant UED Team.Cnt Design, a design language for background applications, is refined by Ant UED Team. Dnt Design, a design language for background applications, is refined by Ant UED Team. Ent Design, a design language for background applications, is refined by Ant UED Team.
</div>
</div>
`;
exports[`renders ./components/typography/demo/interactive.md correctly 1`] = `
<div>
<div
class="ant-typography"
>
This is an editable text.
<button
aria-label="edit"
class="ant-typography-edit"
style="border:0;background:transparent;padding:0;line-height:inherit"
>
<i
aria-label="icon: edit"
class="anticon anticon-edit"
role="button"
>
<svg
aria-hidden="true"
class=""
data-icon="edit"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M257.7 752c2 0 4-.2 6-.5L431.9 722c2-.4 3.9-1.3 5.3-2.8l423.9-423.9a9.96 9.96 0 0 0 0-14.1L694.9 114.9c-1.9-1.9-4.4-2.9-7.1-2.9s-5.2 1-7.1 2.9L256.8 538.8c-1.5 1.5-2.4 3.3-2.8 5.3l-29.5 168.2a33.5 33.5 0 0 0 9.4 29.8c6.6 6.4 14.9 9.9 23.8 9.9zm67.4-174.4L687.8 215l73.3 73.3-362.7 362.6-88.9 15.7 15.6-89zM880 836H144c-17.7 0-32 14.3-32 32v36c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-36c0-17.7-14.3-32-32-32z"
/>
</svg>
</i>
</button>
</div>
<div
class="ant-typography"
>
This is a copyable text.
<button
aria-label="copy"
class="ant-typography-copy"
style="border:0;background:transparent;padding:0;line-height:inherit"
>
<i
aria-label="icon: copy"
class="anticon anticon-copy"
role="button"
>
<svg
aria-hidden="true"
class=""
data-icon="copy"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M832 64H296c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h496v688c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V96c0-17.7-14.3-32-32-32zM704 192H192c-17.7 0-32 14.3-32 32v530.7c0 8.5 3.4 16.6 9.4 22.6l173.3 173.3c2.2 2.2 4.7 4 7.4 5.5v1.9h4.2c3.5 1.3 7.2 2 11 2H704c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32zM350 856.2L263.9 770H350v86.2zM664 888H414V746c0-22.1-17.9-40-40-40H232V264h432v624z"
/>
</svg>
</i>
</button>
</div>
<div
class="ant-typography"
>
Replace copy text.
<button
aria-label="copy"
class="ant-typography-copy"
style="border:0;background:transparent;padding:0;line-height:inherit"
>
<i
aria-label="icon: copy"
class="anticon anticon-copy"
role="button"
>
<svg
aria-hidden="true"
class=""
data-icon="copy"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M832 64H296c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h496v688c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V96c0-17.7-14.3-32-32-32zM704 192H192c-17.7 0-32 14.3-32 32v530.7c0 8.5 3.4 16.6 9.4 22.6l173.3 173.3c2.2 2.2 4.7 4 7.4 5.5v1.9h4.2c3.5 1.3 7.2 2 11 2H704c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32zM350 856.2L263.9 770H350v86.2zM664 888H414V746c0-22.1-17.9-40-40-40H232V264h432v624z"
/>
</svg>
</i>
</button>
</div>
</div>
`;
exports[`renders ./components/typography/demo/paragraph-debug.md correctly 1`] = `
<div>
<h1
class="ant-typography"
>
Introduction
</h1>
<div
class="ant-typography"
>
In the process of internal desktop applications development, many different design specs and implementations would be involved, which might cause designers and developers difficulties and duplication and reduce the efficiency of development.
</div>
<div
class="ant-typography"
>
After massive project practice and summaries, Ant Design, a design language for background applications, is refined by Ant UED Team, which aims to
<span
class="ant-typography"
>
<strong>
uniform the user interface specs for internal background projects, lower the unnecessary cost of design differences and implementation and liberate the resources of design and front-end development
</strong>
</span>
.
</div>
<h2
class="ant-typography"
>
Guidelines and Resources
</h2>
<div
class="ant-typography"
>
We supply a series of design principles, practical patterns and high quality design resources (
<span
class="ant-typography"
>
<code>
Sketch
</code>
</span>
and
<span
class="ant-typography"
>
<code>
Axure
</code>
</span>
), to help people create their product prototypes beautifully and efficiently.
</div>
<div
class="ant-typography"
>
<ul>
<li>
<a
href="/docs/spec/proximity"
>
Principles
</a>
</li>
<li>
<a
href="/docs/pattern/navigation"
>
Patterns
</a>
</li>
<li>
<a
href="/docs/resource/download"
>
Resource Download
</a>
</li>
</ul>
</div>
<h1
class="ant-typography"
id="intro"
>
介绍
</h1>
<div
class="ant-typography"
>
蚂蚁的企业级产品是一个庞大且复杂的体系。这类产品不仅量级巨大且功能复杂,而且变动和并发频繁,常常需要设计与开发能够快速的做出响应。同时这类产品中有存在很多类似的页面以及组件,可以通过抽象得到一些稳定且高复用性的内容。
</div>
<div
class="ant-typography"
>
随着商业化的趋势,越来越多的企业级产品对更好的用户体验有了进一步的要求。带着这样的一个终极目标,我们(蚂蚁金服体验技术部)经过大量的项目实践和总结,逐步打磨出一个服务于企业级产品的设计体系 Ant Design。基于
<span
class="ant-typography"
>
<mark>
『确定』和『自然』
</mark>
</span>
的设计价值观,通过模块化的解决方案,降低冗余的生产成本,让设计者专注于
<span
class="ant-typography"
>
<strong>
更好的用户体验
</strong>
</span>
</div>
<h2
class="ant-typography"
>
设计资源
</h2>
<div
class="ant-typography"
>
我们提供完善的设计原则、最佳实践和设计资源文件(
<span
class="ant-typography"
>
<code>
Sketch
</code>
</span>
<span
class="ant-typography"
>
<code>
Axure
</code>
</span>
),来帮助业务快速设计出高质量的产品原型。
</div>
<div
class="ant-typography"
>
<ul>
<li>
<a
href="/docs/spec/proximity"
>
设计原则
</a>
</li>
<li>
<a
href="/docs/pattern/navigation"
>
设计模式
</a>
</li>
<li>
<a
href="/docs/resource/download"
>
设计资源
</a>
</li>
</ul>
</div>
</div>
`;
exports[`renders ./components/typography/demo/text.md correctly 1`] = `
<div>
<span
class="ant-typography"
>
Ant Design
</span>
<br />
<span
class="ant-typography ant-typography-secondary"
>
Ant Design
</span>
<br />
<span
class="ant-typography ant-typography-warning"
>
Ant Design
</span>
<br />
<span
class="ant-typography ant-typography-danger"
>
Ant Design
</span>
<br />
<span
class="ant-typography ant-typography-disabled"
>
Ant Design
</span>
<br />
<span
class="ant-typography"
>
<mark>
Ant Design
</mark>
</span>
<br />
<span
class="ant-typography"
>
<code>
Ant Design
</code>
</span>
<br />
<span
class="ant-typography"
>
<u>
Ant Design
</u>
</span>
<br />
<span
class="ant-typography"
>
<del>
Ant Design
</del>
</span>
<br />
<span
class="ant-typography"
>
<strong>
Ant Design
</strong>
</span>
</div>
`;
exports[`renders ./components/typography/demo/title.md correctly 1`] = `
<div>
<h1
class="ant-typography"
>
h1. Ant Design
</h1>
<h2
class="ant-typography"
>
h2. Ant Design
</h2>
<h3
class="ant-typography"
>
h3. Ant Design
</h3>
<h4
class="ant-typography"
>
h4. Ant Design
</h4>
</div>
`;

View File

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

View File

@ -0,0 +1,223 @@
import React from 'react';
import { mount } from 'enzyme';
import KeyCode from 'rc-util/lib/KeyCode';
import copy from 'copy-to-clipboard';
import Title from '../Title';
import Paragraph from '../Paragraph';
import Base from '../Base'; // eslint-disable-line import/no-named-as-default
jest.mock('copy-to-clipboard');
describe('Typography', () => {
const LINE_STR_COUNT = 20;
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
// Mock offsetHeight
const originOffsetHeight = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'offsetHeight')
.get;
Object.defineProperty(HTMLElement.prototype, 'offsetHeight', {
get() {
let html = this.innerHTML;
html = html.replace(/<[^>]*>/g, '');
const lines = Math.ceil(html.length / LINE_STR_COUNT);
return lines * 16;
},
});
// Mock getComputedStyle
const originGetComputedStyle = window.getComputedStyle;
window.getComputedStyle = ele => {
const style = originGetComputedStyle(ele);
style.lineHeight = '16px';
return style;
};
beforeAll(() => {
jest.useFakeTimers();
});
afterEach(() => {
errorSpy.mockReset();
});
afterAll(() => {
jest.useRealTimers();
errorSpy.mockRestore();
Object.defineProperty(HTMLElement.prototype, 'offsetHeight', {
get: originOffsetHeight,
});
window.getComputedStyle = originGetComputedStyle;
});
describe('Title', () => {
it('warning if `level` not correct', () => {
mount(<Title level={false} />);
expect(errorSpy).toBeCalledWith(
'Warning: Title only accept `1 | 2 | 3 | 4` as `level` value.',
);
});
});
describe('Base', () => {
describe('trigger ellipsis update', () => {
const fullStr =
'Bamboo is Little Light Bamboo is Little Light Bamboo is Little Light Bamboo is Little Light Bamboo is Little Light';
it('should trigger update', () => {
const wrapper = mount(
<Base ellipsis component="p" editable>
{fullStr}
</Base>,
);
jest.runAllTimers();
wrapper.update();
expect(wrapper.find('span').text()).toEqual('Bamboo is Little ...');
wrapper.setProps({ ellipsis: { rows: 2 } });
jest.runAllTimers();
wrapper.update();
expect(wrapper.find('span').text()).toEqual('Bamboo is Little Light Bamboo is Litt...');
wrapper.setProps({ ellipsis: { rows: 99 } });
jest.runAllTimers();
wrapper.update();
expect(wrapper.find('p').text()).toEqual(fullStr);
wrapper.unmount();
});
it('connect children', () => {
const wrapper = mount(
<Base ellipsis component="p" editable>
{'Bamboo'}
{' is '}
<code>Little</code>
<code>Light</code>
</Base>,
);
jest.runAllTimers();
wrapper.update();
expect(wrapper.find('span').text()).toEqual('Bamboo is Little...');
});
it('should expandable work', () => {
const onExpand = jest.fn();
const wrapper = mount(
<Base ellipsis={{ expandable: true, onExpand }} component="p" copyable editable>
{fullStr}
</Base>,
);
jest.runAllTimers();
wrapper.update();
wrapper.find('.ant-typography-expand').simulate('click');
expect(onExpand).toBeCalled();
jest.runAllTimers();
wrapper.update();
expect(wrapper.find('p').text()).toEqual(fullStr);
});
it('can use css ellipsis', () => {
const wrapper = mount(<Base ellipsis component="p" />);
expect(wrapper.find('.ant-typography-ellipsis-single-line').length).toBeTruthy();
});
});
describe('copyable', () => {
function copyTest(name, text, target) {
it(name, () => {
const onCopy = jest.fn();
const wrapper = mount(
<Base component="p" copyable={{ text, onCopy }}>
test copy
</Base>,
);
wrapper
.find('.ant-typography-copy')
.first()
.simulate('click');
expect(copy.lastStr).toEqual(target);
wrapper.update();
expect(onCopy).toBeCalled();
expect(wrapper.find('.anticon-check').length).toBeTruthy();
jest.runAllTimers();
wrapper.update();
// Will set back when 3 seconds pass
expect(wrapper.find('.anticon-check').length).toBeFalsy();
});
}
copyTest('basic copy', undefined, 'test copy');
copyTest('customize copy', 'bamboo', 'bamboo');
});
describe('editable', () => {
function testStep(name, submitFunc, expectFunc) {
it(name, () => {
const onStart = jest.fn();
const onChange = jest.fn();
const wrapper = mount(<Paragraph editable={{ onChange, onStart }}>Bamboo</Paragraph>);
wrapper
.find('.ant-typography-edit')
.first()
.simulate('click');
expect(onStart).toBeCalled();
wrapper.find('TextArea').simulate('change', {
target: { value: 'Bamboo' },
});
submitFunc(wrapper);
if (expectFunc) {
expectFunc(onChange);
} else {
expect(onChange).toBeCalledWith('Bamboo');
expect(onChange).toHaveBeenCalledTimes(1);
}
});
}
testStep('by key up', wrapper => {
// Not trigger when inComposition
wrapper.find('TextArea').simulate('compositionStart');
wrapper.find('TextArea').simulate('keyDown', { keyCode: KeyCode.ENTER });
wrapper.find('TextArea').simulate('compositionEnd');
wrapper.find('TextArea').simulate('keyUp', { keyCode: KeyCode.ENTER });
// Now trigger
wrapper.find('TextArea').simulate('keyDown', { keyCode: KeyCode.ENTER });
wrapper.find('TextArea').simulate('keyUp', { keyCode: KeyCode.ENTER });
});
testStep(
'by esc key',
wrapper => {
wrapper.find('TextArea').simulate('keyDown', { keyCode: KeyCode.ESC });
wrapper.find('TextArea').simulate('keyUp', { keyCode: KeyCode.ESC });
},
onChange => {
expect(onChange).not.toBeCalled();
},
);
testStep('by blur', wrapper => {
wrapper.find('TextArea').simulate('blur');
});
});
});
});

View File

@ -0,0 +1,67 @@
---
order: 0
title:
zh-CN: 基本
en-US: Basic
---
## zh-CN
展示文档样例。
## en-US
Display the document sample.
```jsx
import { Typography, Divider } from 'antd';
const { Title, Paragraph, Text } = Typography;
ReactDOM.render(
<Typography>
<Title>Introduction</Title>
<Paragraph>
In the process of internal desktop applications development, many different design specs and implementations would be involved, which might cause designers and developers difficulties and duplication and reduce the efficiency of development.
</Paragraph>
<Paragraph>
After massive project practice and summaries, Ant Design, a design language for background applications, is refined by Ant UED Team, which aims to <Text strong>uniform the user interface specs for internal background projects, lower the unnecessary cost of design differences and implementation and liberate the resources of design and front-end development</Text>.
</Paragraph>
<Title level={2}>Guidelines and Resources</Title>
<Paragraph>
We supply a series of design principles, practical patterns and high quality design resources (<Text code>Sketch</Text> and <Text code>Axure</Text>), to help people create their product prototypes beautifully and efficiently.
</Paragraph>
<Paragraph>
<ul>
<li><a href="/docs/spec/proximity">Principles</a></li>
<li><a href="/docs/pattern/navigation">Patterns</a></li>
<li><a href="/docs/resource/download">Resource Download</a></li>
</ul>
</Paragraph>
<Divider />
<Title>介绍</Title>
<Paragraph>
蚂蚁的企业级产品是一个庞大且复杂的体系。这类产品不仅量级巨大且功能复杂,而且变动和并发频繁,常常需要设计与开发能够快速的做出响应。同时这类产品中有存在很多类似的页面以及组件,可以通过抽象得到一些稳定且高复用性的内容。
</Paragraph>
<Paragraph>
随着商业化的趋势,越来越多的企业级产品对更好的用户体验有了进一步的要求。带着这样的一个终极目标,我们(蚂蚁金服体验技术部)经过大量的项目实践和总结,逐步打磨出一个服务于企业级产品的设计体系 Ant Design。基于<Text mark>『确定』和『自然』</Text>的设计价值观,通过模块化的解决方案,降低冗余的生产成本,让设计者专注于<Text strong>更好的用户体验</Text>
</Paragraph>
<Title level={2}>设计资源</Title>
<Paragraph>
我们提供完善的设计原则、最佳实践和设计资源文件(<Text code>Sketch</Text><Text code>Axure</Text>),来帮助业务快速设计出高质量的产品原型。
</Paragraph>
<Paragraph>
<ul>
<li><a href="/docs/spec/proximity">设计原则</a></li>
<li><a href="/docs/pattern/navigation">设计模式</a></li>
<li><a href="/docs/resource/download">设计资源</a></li>
</ul>
</Paragraph>
</Typography>,
mountNode
);
```

View File

@ -0,0 +1,67 @@
---
order: 99
title:
zh-CN: 省略号 Debug
en-US: Ellipsis Debug
debug: true
---
## zh-CN
多行文本省略。
## en-US
Multiple line ellipsis support.
```jsx
import { Typography, Slider, Switch } from 'antd';
const { Text, Paragraph } = Typography;
class Demo extends React.Component {
state = {
rows: 1,
longText: true,
copyable: false,
editable: false,
expandable: false,
};
onChange = (rows) => {
this.setState({ rows });
};
render() {
const { rows, longText, copyable, editable, expandable } = this.state;
return (
<div>
<Switch checked={longText} checkedChildren="Long Text" onChange={val => this.setState({ longText: val })} />
<Switch onChange={val => this.setState({ copyable: val })} />
<Switch onChange={val => this.setState({ editable: val })} />
<Switch onChange={val => this.setState({ expandable: val })} />
<Slider value={rows} min={1} max={10} onChange={this.onChange} />
{longText ?
<Paragraph ellipsis={{ rows, expandable }} copyable={copyable} editable={editable}>
Ant Design, a design language for background applications, is refined by Ant UED Team.
This is a nest sample <Text code strong delete>Test</Text> case.
{'Bnt Design, a design language for background applications, is refined by Ant UED Team.'}
Cnt Design, a design language for background applications, is refined by Ant UED Team.
Dnt Design, a design language for background applications, is refined by Ant UED Team.
Ent Design, a design language for background applications, is refined by Ant UED Team.
</Paragraph> :
<Paragraph ellipsis={{ rows, expandable }} copyable={copyable} editable={editable}>
{'Hello'}
{'World'}
</Paragraph>
}
</div>
);
}
}
ReactDOM.render(
<Demo />
, mountNode);
```

View File

@ -0,0 +1,42 @@
---
order: 4
title:
zh-CN: 省略号
en-US: Ellipsis
---
## zh-CN
多行文本省略。
## en-US
Multiple line ellipsis support.
```jsx
import { Typography } from 'antd';
const { Paragraph } = Typography;
ReactDOM.render(
<div>
<Paragraph ellipsis>
Ant Design, a design language for background applications, is refined by Ant UED Team.
Ant Design, a design language for background applications, is refined by Ant UED Team.
Ant Design, a design language for background applications, is refined by Ant UED Team.
Ant Design, a design language for background applications, is refined by Ant UED Team.
Ant Design, a design language for background applications, is refined by Ant UED Team.
Ant Design, a design language for background applications, is refined by Ant UED Team.
</Paragraph>
<Paragraph ellipsis={{ rows: 3, expandable: true }}>
Ant Design, a design language for background applications, is refined by Ant UED Team.
Ant Design, a design language for background applications, is refined by Ant UED Team.
Ant Design, a design language for background applications, is refined by Ant UED Team.
Ant Design, a design language for background applications, is refined by Ant UED Team.
Ant Design, a design language for background applications, is refined by Ant UED Team.
Ant Design, a design language for background applications, is refined by Ant UED Team.
</Paragraph>
</div>
, mountNode);
```

View File

@ -0,0 +1,43 @@
---
order: 3
title:
zh-CN: 可交互
en-US: Interactive
---
## zh-CN
提供额外的交互能力。
## en-US
Provide additional interactive capacity.
```jsx
import { Typography } from 'antd';
const { Paragraph } = Typography;
class Demo extends React.Component {
state = {
str: 'This is an editable text.',
};
onChange = (str) => {
console.log('Content change:', str);
this.setState({ str });
};
render() {
return (
<div>
<Paragraph editable={{ onChange: this.onChange }}>{this.state.str}</Paragraph>
<Paragraph copyable>This is a copyable text.</Paragraph>
<Paragraph copyable={{ text: 'Hello, Ant Design!' }}>Replace copy text.</Paragraph>
</div>
);
}
}
ReactDOM.render(<Demo />, mountNode);
```

View File

@ -0,0 +1,66 @@
---
order: 2
title:
zh-CN: 标题与段落
en-US: Title and Paragraph
debug: true
---
## zh-CN
展示标题与段落的组合。
## en-US
Display the title and paragraph.
```jsx
import { Typography } from 'antd';
const { Title, Paragraph, Text } = Typography;
ReactDOM.render(
<div>
<Title>Introduction</Title>
<Paragraph>
In the process of internal desktop applications development, many different design specs and implementations would be involved, which might cause designers and developers difficulties and duplication and reduce the efficiency of development.
</Paragraph>
<Paragraph>
After massive project practice and summaries, Ant Design, a design language for background applications, is refined by Ant UED Team, which aims to <Text strong>uniform the user interface specs for internal background projects, lower the unnecessary cost of design differences and implementation and liberate the resources of design and front-end development</Text>.
</Paragraph>
<Title level={2}>Guidelines and Resources</Title>
<Paragraph>
We supply a series of design principles, practical patterns and high quality design resources (<Text code>Sketch</Text> and <Text code>Axure</Text>), to help people create their product prototypes beautifully and efficiently.
</Paragraph>
<Paragraph>
<ul>
<li><a href="/docs/spec/proximity">Principles</a></li>
<li><a href="/docs/pattern/navigation">Patterns</a></li>
<li><a href="/docs/resource/download">Resource Download</a></li>
</ul>
</Paragraph>
<Title id="intro">介绍</Title>
<Paragraph>
蚂蚁的企业级产品是一个庞大且复杂的体系。这类产品不仅量级巨大且功能复杂,而且变动和并发频繁,常常需要设计与开发能够快速的做出响应。同时这类产品中有存在很多类似的页面以及组件,可以通过抽象得到一些稳定且高复用性的内容。
</Paragraph>
<Paragraph>
随着商业化的趋势,越来越多的企业级产品对更好的用户体验有了进一步的要求。带着这样的一个终极目标,我们(蚂蚁金服体验技术部)经过大量的项目实践和总结,逐步打磨出一个服务于企业级产品的设计体系 Ant Design。基于<Text mark>『确定』和『自然』</Text>的设计价值观,通过模块化的解决方案,降低冗余的生产成本,让设计者专注于<Text strong>更好的用户体验</Text>
</Paragraph>
<Title level={2}>设计资源</Title>
<Paragraph>
我们提供完善的设计原则、最佳实践和设计资源文件(<Text code>Sketch</Text><Text code>Axure</Text>),来帮助业务快速设计出高质量的产品原型。
</Paragraph>
<Paragraph>
<ul>
<li><a href="/docs/spec/proximity">设计原则</a></li>
<li><a href="/docs/pattern/navigation">设计模式</a></li>
<li><a href="/docs/resource/download">设计资源</a></li>
</ul>
</Paragraph>
</div>,
mountNode
);
```

View File

@ -0,0 +1,45 @@
---
order: 2
title:
zh-CN: 文本组件
en-US: Text Component
---
## zh-CN
内置不同样式的文本。
## en-US
Provides multiple types of text.
```jsx
import { Typography } from 'antd';
const { Text } = Typography;
ReactDOM.render(
<div>
<Text>Ant Design</Text>
<br />
<Text type="secondary">Ant Design</Text>
<br />
<Text type="warning">Ant Design</Text>
<br />
<Text type="danger">Ant Design</Text>
<br />
<Text disabled>Ant Design</Text>
<br />
<Text mark>Ant Design</Text>
<br />
<Text code>Ant Design</Text>
<br />
<Text underline>Ant Design</Text>
<br />
<Text delete>Ant Design</Text>
<br />
<Text strong>Ant Design</Text>
</div>,
mountNode
);
```

View File

@ -0,0 +1,30 @@
---
order: 1
title:
zh-CN: 标题组件
en-US: Title Component
---
## zh-CN
展示不同级别的标题。
## en-US
Display title in different level.
```jsx
import { Typography } from 'antd';
const { Title } = Typography;
ReactDOM.render(
<div>
<Title>h1. Ant Design</Title>
<Title level={2}>h2. Ant Design</Title>
<Title level={3}>h3. Ant Design</Title>
<Title level={4}>h4. Ant Design</Title>
</div>,
mountNode
);
```

View File

@ -0,0 +1,63 @@
---
category: Components
type: General
title: Typography
cols: 1
---
Basic format and regular operation on text.
## When To Use
When need to display title or text content. Like:
* Copy
* Ellipsis / expand
* Edit
* Markdown Typography
## API
### Typography.Text
| Property | Description | Type | Default |
| -------- | ----------- | ---- | ------- |
| copyable | Config copy. Can set copy text and callback when is an object | boolean \| { text: string, onCopy: Function } | false |boolean \| string | false |
| delete | delete line style | boolean | false |
| disabled | Disable content | boolean | false |
| editable | Editable. Can control edit state when is object | boolean \| { editing: boolean, onStart: Function, onChange: Function(string) } | false |
| ellipsis | Display ellipsis when overflow | boolean | false |
| mark | mark style | boolean | false |
| underline | underline style | boolean | false |
| onChange | Trigger when user edit the content | Function(string) | - |
| strong | bold style | boolean | false |
| type | Content type | `secondary`, `warning`, `danger` | - |
### Typography.Title
| Property | Description | Type | Default |
| -------- | ----------- | ---- | ------- |
| copyable | Config copy. Can set copy text and callback when is an object | boolean \| { text: string, onCopy: Function } | false |boolean \| string | false |
| delete | delete line style | boolean | false |
| disabled | Disable content | boolean | false |
| editable | Editable. Can control edit state when is object | boolean \| { editing: boolean, onStart: Function, onChange: Function(string) } | false |
| ellipsis | Display ellipsis when overflow. Can config rows and expandable by using object | boolean \| { rows: number, expandable: boolean, onExpand: Function } | false |
| level | Set content importance. Match with `h1`, `h2`, `h3`, `h4` | number: `1`, `2`, `3`, `4` | 1 |
| mark | mark style | boolean | false |
| underline | underline style | boolean | false |
| onChange | Trigger when user edit the content | Function(string) | - |
| type | Content type | `secondary`, `warning`, `danger` | - |
### Typography.Paragraph
| Property | Description | Type | Default |
| -------- | ----------- | ---- | ------- |
| copyable | Config copy. Can set copy text and callback when is an object | boolean \| { text: string, onCopy: Function } | false |boolean \| string | false |
| delete | delete line style | boolean | false |
| disabled | Disable content | boolean | false |
| editable | Editable. Can control edit state when is object | boolean \| { editing: boolean, onStart: Function, onChange: Function(string) } | false |
| ellipsis | Display ellipsis when overflow. Can config rows and expandable by using object | boolean \| { rows: number, expandable: boolean, onExpand: Function } | false |
| mark | mark style | boolean | false |
| underline | underline style | boolean | false |
| onChange | Trigger when user edit the content | Function(string) | - |
| strong | bold style | boolean | false |
| type | Content type | `secondary`, `warning`, `danger` | - |

View File

@ -0,0 +1,17 @@
import OriginTypography from './Typography';
import Text from './Text';
import Title from './Title';
import Paragraph from './Paragraph';
type TypographyProps = typeof OriginTypography & {
Text: typeof Text;
Title: typeof Title;
Paragraph: typeof Paragraph;
};
const Typography = OriginTypography as TypographyProps;
Typography.Text = Text;
Typography.Title = Title;
Typography.Paragraph = Paragraph;
export default Typography;

View File

@ -0,0 +1,63 @@
---
category: Components
subtitle: 排版
type: 通用
title: Typography
cols: 1
---
文本的基本格式及常见操作。
## 何时使用
当需要展示标题、文本内容时使用。例如:
* 拷贝
* 省略/展开
* 可编辑
* Markdown 排版样式
## API
### Typography.Text
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| copyable | 是否可拷贝,为对象时可设置复制文本以回调函数 | boolean \| { text: string, onCopy: Function } | false |
| delete | 添加删除线样式 | boolean | false |
| disabled | 禁用文本 | boolean | false |
| editable | 是否可编辑,为对象时可对编辑进行控制 | boolean \| { editing: boolean, onStart: Function, onChange: Function(string) } | false |
| ellipsis | 设置自动溢出省略 | boolean | false |
| mark | 添加标记样式 | boolean | false |
| underline | 添加下划线样式 | boolean | false |
| strong | 是否加粗 | boolean | false |
| type | 文本类型 | `secondary`, `warning`, `danger` | - |
### Typography.Title
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| copyable | 是否可拷贝,为对象时可设置复制文本以回调函数 | boolean \| { text: string, onCopy: Function } | false |
| delete | 添加删除线样式 | boolean | false |
| disabled | 禁用文本 | boolean | false |
| editable | 是否可编辑,为对象时可对编辑进行控制 | boolean \| { editing: boolean, onStart: Function, onChange: Function(string) } | false |
| ellipsis | 自动溢出省略,为对象时可设置省略行数与是否可展开等 | boolean \| { rows: number, expandable: boolean, onExpand: Function } | false |
| level | 重要程度,相当于 `h1`、`h2`、`h3`、`h4` | number: `1`, `2`, `3`, `4` | 1 |
| mark | 添加标记样式 | boolean | false |
| underline | 添加下划线样式 | boolean | false |
| onChange | 当用户提交编辑内容时触发 | Function(string) | - |
| type | 文本类型 | `secondary`, `warning`, `danger` | - |
### Typography.Paragraph
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| delete | 添加删除线样式 | boolean | false |
| disabled | 禁用文本 | boolean | false |
| editable | 是否可编辑,为对象时可对编辑进行控制 | boolean \| { editing: boolean, onStart: Function, onChange: Function(string) } | false |
| ellipsis | 自动溢出省略,为对象时可设置省略行数与是否可展开等 | boolean \| { rows: number, expandable: boolean, onExpand: Function } | false |
| mark | 添加标记样式 | boolean | false |
| underline | 添加下划线样式 | boolean | false |
| onChange | 当用户提交编辑内容时触发 | Function(string) | - |
| strong | 是否加粗 | boolean | false |
| type | 文本类型 | `secondary`, `warning`, `danger` | - |

View File

@ -0,0 +1,241 @@
@import '../../style/themes/default';
@import '../../style/mixins/index';
@typography-prefix-cls: ~'@{ant-prefix}-typography';
@typography-title-margin-top: 1.2em;
// =============== Common ===============
.typography-paragraph() {
margin-bottom: 1em;
}
.typography-title(@fontSize; @lineHeight) {
margin-bottom: 0.5em;
color: @heading-color;
font-weight: 600;
font-size: @fontSize;
line-height: @lineHeight;
}
.typography-title-1() {
.typography-title(@heading-1-size, 1.23);
}
.typography-title-2() {
.typography-title(@heading-2-size, 1.35);
}
.typography-title-3() {
.typography-title(@heading-3-size, 1.35);
}
.typography-title-4() {
.typography-title(@heading-4-size, 1.4);
}
.operation-unit() {
color: @link-color;
text-decoration: none;
outline: none;
cursor: pointer;
transition: color 0.3s;
&:focus,
&:hover {
color: @link-hover-color;
}
&:focus {
text-decoration: underline;
text-decoration-skip-ink: auto;
}
&:active {
color: @link-active-color;
}
}
// =============== Basic ===============
.@{typography-prefix-cls} {
color: @text-color;
&-secondary {
color: @text-color-secondary;
}
&-warning {
color: @text-color-warning;
}
&-danger {
color: @text-color-danger;
}
&-disabled {
color: @disabled-color;
cursor: not-allowed;
user-select: none;
}
// Tag
div&,
p {
.typography-paragraph();
}
h1&,
h1 {
.typography-title-1();
}
h2&,
h2 {
.typography-title-2();
}
h3&,
h3 {
.typography-title-3();
}
h4&,
h4 {
.typography-title-4();
}
h1&,
h2&,
h3&,
h4& {
.@{typography-prefix-cls} + & {
margin-top: @typography-title-margin-top;
}
}
div,
ul,
li,
p,
h1,
h2,
h3,
h4 {
+ h1,
+ h2,
+ h3,
+ h4 {
margin-top: @typography-title-margin-top;
}
}
span&-ellipsis {
display: inline-block;
}
a {
.operation-unit();
&:active,
&:hover {
text-decoration: @link-hover-decoration;
}
&[disabled] {
color: @disabled-color;
cursor: not-allowed;
pointer-events: none;
}
}
code {
margin: 0 0.2em;
padding: 0.2em 0.4em 0.1em;
font-size: 85%;
background: rgba(0, 0, 0, 0.06);
border: 1px solid rgba(0, 0, 0, 0.06);
border-radius: 3px;
}
mark {
padding: 0;
background-color: @gold-3;
}
u,
ins {
text-decoration: underline;
text-decoration-skip-ink: auto;
}
s,
del {
text-decoration: line-through;
}
strong {
font-weight: 600;
}
// Operation
&-expand,
&-edit,
&-copy {
.operation-unit();
margin-left: 8px;
}
&-copy-success {
&,
&:hover,
&:focus {
color: @success-color;
}
}
// Text input area
&-edit-content {
position: relative;
&-confirm {
position: absolute;
right: 10px;
bottom: 8px;
color: @text-color-secondary;
pointer-events: none;
}
}
// list
ul,
ol {
margin: 0 0 1em 0;
padding: 0;
li {
margin: 0 0 0 20px;
padding: 0 0 0 4px;
}
}
ul li {
list-style-type: circle;
li {
list-style-type: disc;
}
}
ol li {
list-style-type: decimal;
}
// ============ Ellipsis ============
&-ellipsis-single-line {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
&-ellipsis-multiple-line {
display: -webkit-box;
-webkit-line-clamp: 3;
/*! autoprefixer: ignore next */
-webkit-box-orient: vertical;
overflow: hidden;
}
}

Some files were not shown because too many files have changed in this diff Show More