mirror of
https://github.com/ant-design/ant-design.git
synced 2024-11-24 19:19:57 +08:00
commit
2996d8e625
@ -14,7 +14,30 @@ 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
|
||||
|
||||
`2019-02-23`
|
||||
|
@ -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)
|
||||
|
@ -37,6 +37,7 @@ Array [
|
||||
"Modal",
|
||||
"Statistic",
|
||||
"notification",
|
||||
"PageHeader",
|
||||
"Pagination",
|
||||
"Popconfirm",
|
||||
"Popover",
|
||||
@ -59,6 +60,7 @@ Array [
|
||||
"TimePicker",
|
||||
"Timeline",
|
||||
"Tooltip",
|
||||
"Typography",
|
||||
"Mention",
|
||||
"Upload",
|
||||
"version",
|
||||
|
@ -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];
|
||||
};
|
||||
|
61
components/_util/resizeObserver.tsx
Normal file
61
components/_util/resizeObserver.tsx
Normal 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;
|
68
components/_util/transButton.tsx
Normal file
68
components/_util/transButton.tsx
Normal 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;
|
@ -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 tuple = <T extends string[]>(...args: T) => args;
|
||||
|
||||
export const tupleNum = <T extends number[]>(...args: T) => args;
|
||||
|
@ -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>
|
||||
|
@ -28,6 +28,7 @@ interface PanelProps {
|
||||
showArrow?: boolean;
|
||||
forceRender?: boolean;
|
||||
disabled?: boolean;
|
||||
extra?: React.ReactNode;
|
||||
}
|
||||
|
||||
export default class Collapse extends React.Component<CollapseProps, any> {
|
||||
|
@ -13,6 +13,7 @@ export interface CollapsePanelProps {
|
||||
prefixCls?: string;
|
||||
forceRender?: boolean;
|
||||
id?: string;
|
||||
extra?: React.ReactNode;
|
||||
}
|
||||
|
||||
export default class CollapsePanel extends React.Component<CollapsePanelProps, {}> {
|
||||
|
@ -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"
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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
|
||||
|
||||
|
@ -36,6 +36,7 @@ cols: 1
|
||||
| header | 面板头内容 | string\|ReactNode | 无 |
|
||||
| key | 对应 activeKey | string | 无 |
|
||||
| showArrow | 是否展示当前面板上的箭头 | boolean | `true` |
|
||||
| extra | 自定义渲染每个面板右上角的内容 | ReactNode | - |
|
||||
|
||||
## FAQ
|
||||
|
||||
|
@ -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`] = `
|
||||
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
const { wrapperCol } = this.props;
|
||||
const className = classNames(
|
||||
`${prefixCls}-item-control-wrapper`,
|
||||
wrapperCol && wrapperCol.className,
|
||||
);
|
||||
return (
|
||||
<Col {...wrapperCol} className={className} key="wrapper">
|
||||
{children}
|
||||
</Col>
|
||||
<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`,
|
||||
mergedWrapperCol.className,
|
||||
);
|
||||
|
||||
// No pass FormContext since it's useless
|
||||
return (
|
||||
<FormContext.Provider value={{ vertical }}>
|
||||
<Col {...mergedWrapperCol} className={className} key="wrapper">
|
||||
{children}
|
||||
</Col>
|
||||
</FormContext.Provider>
|
||||
);
|
||||
}}
|
||||
</FormContext.Consumer>
|
||||
);
|
||||
}
|
||||
|
||||
@ -323,35 +327,43 @@ export default class FormItem extends React.Component<FormItemProps, any> {
|
||||
};
|
||||
|
||||
renderLabel(prefixCls: string) {
|
||||
const { label, labelCol, colon, id } = this.props;
|
||||
const context = this.context;
|
||||
const required = this.isRequired();
|
||||
return (
|
||||
<FormContext.Consumer>
|
||||
{({ vertical, labelCol: contextLabelCol }: FormContextProps) => {
|
||||
const { label, labelCol, colon, id } = this.props;
|
||||
const required = this.isRequired();
|
||||
|
||||
const labelColClassName = classNames(`${prefixCls}-item-label`, labelCol && labelCol.className);
|
||||
const labelClassName = classNames({
|
||||
[`${prefixCls}-item-required`]: required,
|
||||
});
|
||||
const mergedLabelCol: ColProps =
|
||||
('labelCol' in this.props ? labelCol : contextLabelCol) || {};
|
||||
|
||||
let labelChildren = label;
|
||||
// Keep label is original where there should have no colon
|
||||
const haveColon = colon && !context.vertical;
|
||||
// Remove duplicated user input colon
|
||||
if (haveColon && typeof label === 'string' && (label as string).trim() !== '') {
|
||||
labelChildren = (label as string).replace(/[:|:]\s*$/, '');
|
||||
}
|
||||
const labelColClassName = classNames(`${prefixCls}-item-label`, mergedLabelCol.className);
|
||||
const labelClassName = classNames({
|
||||
[`${prefixCls}-item-required`]: required,
|
||||
});
|
||||
|
||||
return label ? (
|
||||
<Col {...labelCol} className={labelColClassName} key="label">
|
||||
<label
|
||||
htmlFor={id || this.getId()}
|
||||
className={labelClassName}
|
||||
title={typeof label === 'string' ? label : ''}
|
||||
onClick={this.onLabelClick}
|
||||
>
|
||||
{labelChildren}
|
||||
</label>
|
||||
</Col>
|
||||
) : null;
|
||||
let labelChildren = label;
|
||||
// Keep label is original where there should have no colon
|
||||
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 {...mergedLabelCol} className={labelColClassName} key="label">
|
||||
<label
|
||||
htmlFor={id || this.getId()}
|
||||
className={labelClassName}
|
||||
title={typeof label === 'string' ? label : ''}
|
||||
onClick={this.onLabelClick}
|
||||
>
|
||||
{labelChildren}
|
||||
</label>
|
||||
</Col>
|
||||
) : null;
|
||||
}}
|
||||
</FormContext.Consumer>
|
||||
);
|
||||
}
|
||||
|
||||
renderChildren(prefixCls: string) {
|
||||
|
@ -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"
|
||||
|
12
components/form/context.ts
Normal file
12
components/form/context.ts
Normal 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,
|
||||
});
|
@ -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!' }],
|
||||
|
@ -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
|
||||
@ -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."
|
||||
>
|
||||
|
@ -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)(
|
||||
|
@ -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">
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
||||
|
@ -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) | |
|
||||
|
||||
### 校验规则
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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';
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
@ -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,14 +126,16 @@ class TextArea extends React.Component<TextAreaProps, TextAreaState> {
|
||||
otherProps.value = otherProps.value || '';
|
||||
}
|
||||
return (
|
||||
<textarea
|
||||
{...otherProps}
|
||||
className={cls}
|
||||
style={style}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
onChange={this.handleTextareaChange}
|
||||
ref={this.saveTextAreaRef}
|
||||
/>
|
||||
<ResizeObserver onResize={this.resizeOnNextFrame} disabled={!autosize}>
|
||||
<textarea
|
||||
{...otherProps}
|
||||
className={cls}
|
||||
style={style}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
onChange={this.handleTextareaChange}
|
||||
ref={this.saveTextAreaRef}
|
||||
/>
|
||||
</ResizeObserver>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -625,13 +625,18 @@ exports[`TextArea should support disabled 1`] = `
|
||||
disabled={true}
|
||||
>
|
||||
<Consumer>
|
||||
<textarea
|
||||
className="ant-input ant-input-disabled"
|
||||
<ReactResizeObserver
|
||||
disabled={true}
|
||||
onChange={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
style={Object {}}
|
||||
/>
|
||||
onResize={[Function]}
|
||||
>
|
||||
<textarea
|
||||
className="ant-input ant-input-disabled"
|
||||
disabled={true}
|
||||
onChange={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
style={Object {}}
|
||||
/>
|
||||
</ReactResizeObserver>
|
||||
</Consumer>
|
||||
</TextArea>
|
||||
`;
|
||||
@ -641,13 +646,18 @@ exports[`TextArea should support maxLength 1`] = `
|
||||
maxLength={10}
|
||||
>
|
||||
<Consumer>
|
||||
<textarea
|
||||
className="ant-input"
|
||||
maxLength={10}
|
||||
onChange={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
style={Object {}}
|
||||
/>
|
||||
<ReactResizeObserver
|
||||
disabled={true}
|
||||
onResize={[Function]}
|
||||
>
|
||||
<textarea
|
||||
className="ant-input"
|
||||
maxLength={10}
|
||||
onChange={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
style={Object {}}
|
||||
/>
|
||||
</ReactResizeObserver>
|
||||
</Consumer>
|
||||
</TextArea>
|
||||
`;
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -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>
|
||||
`;
|
||||
|
@ -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>
|
||||
`;
|
||||
|
@ -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;
|
||||
|
@ -47,4 +47,10 @@ export default {
|
||||
Icon: {
|
||||
icon: 'icon',
|
||||
},
|
||||
Text: {
|
||||
edit: 'edit',
|
||||
copy: 'copy',
|
||||
copied: 'copy success',
|
||||
expand: 'expand',
|
||||
},
|
||||
};
|
||||
|
@ -47,4 +47,10 @@ export default {
|
||||
Icon: {
|
||||
icon: '图标',
|
||||
},
|
||||
Text: {
|
||||
edit: '编辑',
|
||||
copy: '复制',
|
||||
copied: '复制成功',
|
||||
expand: '展开',
|
||||
},
|
||||
};
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
636
components/page-header/__tests__/__snapshots__/demo.test.js.snap
Normal file
636
components/page-header/__tests__/__snapshots__/demo.test.js.snap
Normal 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>
|
||||
`;
|
3
components/page-header/__tests__/demo.test.js
Normal file
3
components/page-header/__tests__/demo.test.js
Normal file
@ -0,0 +1,3 @@
|
||||
import demoTest from '../../../tests/shared/demoTest';
|
||||
|
||||
demoTest('page-header');
|
41
components/page-header/__tests__/index.test.js
Normal file
41
components/page-header/__tests__/index.test.js
Normal 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();
|
||||
});
|
||||
});
|
121
components/page-header/demo/actions.md
Normal file
121
components/page-header/demo/actions.md
Normal 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>
|
34
components/page-header/demo/basic.md
Normal file
34
components/page-header/demo/basic.md
Normal 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>
|
42
components/page-header/demo/breadcrumb.md
Normal file
42
components/page-header/demo/breadcrumb.md
Normal 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
|
||||
);
|
||||
|
||||
```
|
115
components/page-header/demo/content.md
Normal file
115
components/page-header/demo/content.md
Normal 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'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>
|
27
components/page-header/index.en-US.md
Normal file
27
components/page-header/index.en-US.md
Normal 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()` |
|
||||
|
102
components/page-header/index.tsx
Normal file
102
components/page-header/index.tsx
Normal 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;
|
27
components/page-header/index.zh-CN.md
Normal file
27
components/page-header/index.zh-CN.md
Normal 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()` |
|
||||
|
92
components/page-header/style/index.less
Normal file
92
components/page-header/style/index.less
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
5
components/page-header/style/index.tsx
Normal file
5
components/page-header/style/index.tsx
Normal file
@ -0,0 +1,5 @@
|
||||
import './index.less';
|
||||
|
||||
import '../../divider/style';
|
||||
import '../../breadcrumb/style';
|
||||
import '../../typography/style';
|
@ -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;
|
||||
|
@ -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 | - |
|
||||
|
@ -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 | - |
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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}
|
||||
|
@ -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 {
|
||||
|
@ -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 = () => {
|
||||
clearInterval(this.countdownId);
|
||||
this.countdownId = undefined;
|
||||
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) => {
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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" />
|
||||
|
@ -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 | - |
|
||||
|
@ -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 | - |
|
||||
|
@ -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;
|
||||
|
@ -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?:
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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 | - |
|
||||
|
@ -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) => (
|
||||
|
@ -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 | 无 |
|
||||
|
@ -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%;
|
||||
}
|
||||
|
483
components/typography/Base.tsx
Normal file
483
components/typography/Base.tsx
Normal 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);
|
133
components/typography/Editable.tsx
Normal file
133
components/typography/Editable.tsx
Normal 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;
|
8
components/typography/Paragraph.tsx
Normal file
8
components/typography/Paragraph.tsx
Normal 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;
|
18
components/typography/Text.tsx
Normal file
18
components/typography/Text.tsx
Normal 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;
|
24
components/typography/Title.tsx
Normal file
24
components/typography/Title.tsx
Normal 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;
|
47
components/typography/Typography.tsx
Normal file
47
components/typography/Typography.tsx
Normal 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;
|
638
components/typography/__tests__/__snapshots__/demo.test.js.snap
Normal file
638
components/typography/__tests__/__snapshots__/demo.test.js.snap
Normal 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>
|
||||
`;
|
3
components/typography/__tests__/demo.test.js
Normal file
3
components/typography/__tests__/demo.test.js
Normal file
@ -0,0 +1,3 @@
|
||||
import demoTest from '../../../tests/shared/demoTest';
|
||||
|
||||
demoTest('typography');
|
223
components/typography/__tests__/index.test.js
Normal file
223
components/typography/__tests__/index.test.js
Normal 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');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
67
components/typography/demo/basic.md
Normal file
67
components/typography/demo/basic.md
Normal 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
|
||||
);
|
||||
```
|
67
components/typography/demo/ellipsis-debug.md
Normal file
67
components/typography/demo/ellipsis-debug.md
Normal 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);
|
||||
```
|
42
components/typography/demo/ellipsis.md
Normal file
42
components/typography/demo/ellipsis.md
Normal 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);
|
||||
```
|
43
components/typography/demo/interactive.md
Normal file
43
components/typography/demo/interactive.md
Normal 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);
|
||||
```
|
66
components/typography/demo/paragraph-debug.md
Normal file
66
components/typography/demo/paragraph-debug.md
Normal 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
|
||||
);
|
||||
```
|
45
components/typography/demo/text.md
Normal file
45
components/typography/demo/text.md
Normal 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
|
||||
);
|
||||
```
|
30
components/typography/demo/title.md
Normal file
30
components/typography/demo/title.md
Normal 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
|
||||
);
|
||||
```
|
63
components/typography/index.en-US.md
Normal file
63
components/typography/index.en-US.md
Normal 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` | - |
|
17
components/typography/index.tsx
Normal file
17
components/typography/index.tsx
Normal 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;
|
63
components/typography/index.zh-CN.md
Normal file
63
components/typography/index.zh-CN.md
Normal 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` | - |
|
241
components/typography/style/index.less
Normal file
241
components/typography/style/index.less
Normal 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
Loading…
Reference in New Issue
Block a user