chore: merge master into feature (#26421)

This commit is contained in:
xrkffgg 2020-08-26 21:07:43 +08:00 committed by GitHub
commit a8b4e3c2e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 11669 additions and 6426 deletions

View File

@ -26,6 +26,7 @@ timeline: true
`2020-08-23`
- 💄 Darker `@text-color` for WCAG 2.0 on contrast ratio. [#25630](https://github.com/ant-design/ant-design/pull/25630)
- 🔥 New Image component. [#26296](https://github.com/ant-design/ant-design/pull/26296)
- 🔥 Table support `sticky` prop to sticky header and scroll bar. [#25939](https://github.com/ant-design/ant-design/pull/25939)
- Form
@ -63,10 +64,8 @@ timeline: true
- 💄 Optimize the display effect of Descriptions when there is more content. [#25903](https://github.com/ant-design/ant-design/pull/25903)
- 🆕 message could be detroied by `message.desctroy(key)`. [#26052](https://github.com/ant-design/ant-design/pull/26052) [@lihqi](https://github.com/lihqi)
- 💄 Adjust InputNumber handler bar to be hidden when `readOnly`. [#25998](https://github.com/ant-design/ant-design/pull/25998)
- 💄 Lighten `@text-color` color in dark theme. [#25923](https://github.com/ant-design/ant-design/pull/25923)
- 💄 Darken `@text-color` color in default theme. [#25630](https://github.com/ant-design/ant-design/pull/25630)
- Locale
- 🏴󠁥󠁳󠁧󠁡󠁿 Add Galician Locale support. [#26015](https://github.com/ant-design/ant-design/pull/26015) [@barreeeiroo](https://github.com/barreeeiroo)
- 🌐 Add Galician locale support. [#26015](https://github.com/ant-design/ant-design/pull/26015) [@barreeeiroo](https://github.com/barreeeiroo)
- 🇱🇹 Add Lithuanian locale support. [#26312](https://github.com/ant-design/ant-design/pull/26312) [@mslotvinskij](https://github.com/mslotvinskij)
- 🌐 Use `kmr_IQ` to replace `ku_IQ`. [#26030](https://github.com/ant-design/ant-design/pull/26030)
- RTL

View File

@ -26,6 +26,7 @@ timeline: true
`2020-08-23`
- 💄 加深默认文本 `@text-color` 以满足 WCAG 2.0 对比度的规范。[#25630](https://github.com/ant-design/ant-design/pull/25630)
- 🔥 新增图片组件 Image。[#26296](https://github.com/ant-design/ant-design/pull/26296)
- 🔥 Table 新增 `sticky` 属性以支持固定表头和滚动条。[#25939](https://github.com/ant-design/ant-design/pull/25939)
- Form
@ -33,22 +34,22 @@ timeline: true
- 🆕 Form.List 中的 `add` 方法支持第二个 `index` 参数。[#26081](https://github.com/ant-design/ant-design/pull/26081)
- 🆕 虚拟滚动支持无闪动滚动,修复 Select/TreeSelect 滚动时列表空白的问题。[#26306](https://github.com/ant-design/ant-design/pull/26306)
- Typography
- 🆕 新增 Typography.Text success 类型。[#26145](https://github.com/ant-design/ant-design/pull/26145) [@llwslc](https://github.com/llwslc)
- 🆕 新增 Typography.Text `success` 类型。[#26145](https://github.com/ant-design/ant-design/pull/26145) [@llwslc](https://github.com/llwslc)
- 🆕 Typography `copyable` 支持隐藏提示,`editable` 支持设置图标与提示。[#25953](https://github.com/ant-design/ant-design/pull/25953) [@llwslc](https://github.com/llwslc)
- 🆕 新增 Typography.Title 5 级标题。[#25861](https://github.com/ant-design/ant-design/pull/25861)
- 🆕 Typography 的 `editable` 配置中增加了 `maxLength` & `autoSize` 支持。[#25373](https://github.com/ant-design/ant-design/pull/25373) [@CornerSkyless](https://github.com/CornerSkyless)
- 🆕 Typography 的 `editable` 配置中增加了 `maxLength` `autoSize` 属性。[#25373](https://github.com/ant-design/ant-design/pull/25373) [@CornerSkyless](https://github.com/CornerSkyless)
- 🐞 修复 Transfer 搜索空格时 `filterOption` 没有触发的问题。[#26335](https://github.com/ant-design/ant-design/pull/26335)
- Progress
- 🐞 修复 Progress `steps` 属性对于 `trailColor` 不生效的问题。[#26323](https://github.com/ant-design/ant-design/pull/26323)
- 🐞 修复 Progress 当 `type="circle"``success.percent` 不生效的问题。[#26307](https://github.com/ant-design/ant-design/pull/26307)
- 🐞 修复 Textarea 当 `value``undefined` 时未显示 `defaultValue` 问题。[#26327](https://github.com/ant-design/ant-design/pull/26327)
- Cascader
- 🐞 修复 Cascader 在按下 ESC 键,然后通过输入进行搜索时 options 不展开的问题。[#26271](https://github.com/ant-design/ant-design/pull/26271) [@flyerH](https://github.com/flyerH)
- 🐞 修复 Cascader 在按下 ESC 键,然后通过输入进行搜索时 `options` 不展开的问题。[#26271](https://github.com/ant-design/ant-design/pull/26271) [@flyerH](https://github.com/flyerH)
- 💄 优化 Cascader 清除动画效果。[#26186](https://github.com/ant-design/ant-design/pull/26186)
- 🗑 移除遗留的 Button.Group 支持,请使用 Space 代替。[#26260](https://github.com/ant-design/ant-design/pull/26260)
- Select
- 🆕 Select 支持 onClear 属性。[#25907](https://github.com/ant-design/ant-design/pull/25907)
- 🐞 修复 Select mode="tags" 搜索显示两条重复条目的问题。[#25907](https://github.com/ant-design/ant-design/pull/25907)
- 🆕 Select 支持 `onClear` 属性。[#25907](https://github.com/ant-design/ant-design/pull/25907)
- 🐞 修复 Select `mode="tags"` 搜索显示两条重复条目的问题。[#25907](https://github.com/ant-design/ant-design/pull/25907)
- 🐞 修复 Select 聚焦时被禁用的样式异常问题。[#26255](https://github.com/ant-design/ant-design/pull/26255)
- 🐞 修复多选模式的 Select 在 `showArrow` 时图标重叠问题。[#26168](https://github.com/ant-design/ant-design/pull/26168) [@zhangchen915](https://github.com/zhangchen915)
- DatePicker
@ -63,12 +64,10 @@ timeline: true
- 💄 优化 Descriptions 在内容比较多时的显示效果。[#25903](https://github.com/ant-design/ant-design/pull/25903)
- 🆕 message 支持通过 `message.desctroy(key)` 销毁。[#26052](https://github.com/ant-design/ant-design/pull/26052) [@lihqi](https://github.com/lihqi)
- 💄 调整 InputNumber 操作栏在 `readOnly` 时为隐藏。[#25998](https://github.com/ant-design/ant-design/pull/25998)
- 💄 加深暗色主题下 `@text-color` 颜色。[#25923](https://github.com/ant-design/ant-design/pull/25923)
- 💄 加深默认主题下 `@text-color` 颜色。[#25630](https://github.com/ant-design/ant-design/pull/25630)
- 国际化
- 🏴󠁥󠁳󠁧󠁡󠁿 添加加利西亚语支持。[#26015](https://github.com/ant-design/ant-design/pull/26015) [@barreeeiroo](https://github.com/barreeeiroo)
- 🌐 添加加利西亚语支持。[#26015](https://github.com/ant-design/ant-design/pull/26015) [@barreeeiroo](https://github.com/barreeeiroo)
- 🇱🇹 添加立陶宛语支持。[#26312](https://github.com/ant-design/ant-design/pull/26312) [@mslotvinskij](https://github.com/mslotvinskij)
- 🌐 `kmr_IQ` 替换 `ku_IQ` 缩写。[#26030](https://github.com/ant-design/ant-design/pull/26030)
- 🌐 新增 `kmr_IQ` 语言包用以代替 ku_IQ。[#26030](https://github.com/ant-design/ant-design/pull/26030)
- RTL
- 💄 优化 Tree RTL 模式下连接线的样式。[#26205](https://github.com/ant-design/ant-design/pull/26205)
- 💄 优化 Dropdown RTL 写法避免暗黑模式样式覆盖。[#26206](https://github.com/ant-design/ant-design/pull/26206)

View File

@ -13,6 +13,7 @@ class AffixMounter extends React.Component<{
offsetBottom?: number;
offsetTop?: number;
onTestUpdatePosition?(): void;
onChange?: () => void;
}> {
private container: HTMLDivElement;
@ -131,13 +132,15 @@ describe('Affix Render', () => {
it('updatePosition when offsetTop changed', async () => {
document.body.innerHTML = '<div id="mounter" />';
const onChange = jest.fn();
affixMounterWrapper = mount(<AffixMounter offsetTop={0} />, {
affixMounterWrapper = mount(<AffixMounter offsetTop={0} onChange={onChange} />, {
attachTo: document.getElementById('mounter'),
});
await sleep(20);
await movePlaceholder(-100);
expect(onChange).toHaveBeenLastCalledWith(true);
expect(affixMounterWrapper.instance().affix.state.affixStyle?.top).toBe(0);
affixMounterWrapper.setProps({
offsetTop: 10,

View File

@ -141,6 +141,17 @@ describe('Badge', () => {
expect(wrapper).toMatchSnapshot();
});
it('Badge should work when status/color is empty string', () => {
const wrapper = mount(
<>
<Badge color="" text="text" />
<Badge status="" text="text" />
</>,
);
expect(wrapper.find('.ant-badge')).toHaveLength(2);
});
it('render Badge size when contains children', () => {
const wrapper = render(
<>

View File

@ -62,9 +62,7 @@ const Badge: CompoundedComponent = ({
return displayCount as string | number | null;
};
const hasStatus = (): boolean => {
return !!status || !!color;
};
const hasStatus = (): boolean => (status !== null && status !== undefined) || (color !== null && color !== undefined);
const isZero = () => {
const numberedDisplayCount = getNumberedDisplayCount();

View File

@ -29,11 +29,13 @@ describe('Calendar', () => {
it('Calendar should be selectable', () => {
const onSelect = jest.fn();
const wrapper = mount(<Calendar onSelect={onSelect} />);
const onChange = jest.fn();
const wrapper = mount(<Calendar onSelect={onSelect} onChange={onChange} />);
wrapper.find('.ant-picker-cell').at(0).simulate('click');
expect(onSelect).toHaveBeenCalledWith(expect.anything());
const value = onSelect.mock.calls[0][0];
expect(Moment.isMoment(value)).toBe(true);
expect(onChange).toHaveBeenCalled();
});
it('only Valid range should be selectable', () => {

View File

@ -6055,7 +6055,7 @@ exports[`renders ./components/form/demo/validate-other.md correctly 1`] = `
</svg>
</span>
<span>
Click to upload
Click to upload
</span>
</button>
</span>

View File

@ -177,9 +177,7 @@ const Demo = () => {
extra="longgggggggggggggggggggggggggggggggggg"
>
<Upload name="logo" action="/upload.do" listType="picture">
<Button>
<UploadOutlined /> Click to upload
</Button>
<Button icon={<UploadOutlined />}>Click to upload</Button>
</Upload>
</Form.Item>

View File

@ -338,6 +338,10 @@ Before Modal opens, children elements do not exist in the view. You can set `for
Components inside Form.Item with name property will turn into controlled mode, which makes `defaultValue` not work anymore. Please try `initialValues` of Form to set default value.
### Why can not call `ref` of From at first time?
`ref` only receives the mounted instance. please ref React official doc: https://reactjs.org/docs/refs-and-the-dom.html#accessing-refs
### Why `resetFields` will re-mount component?
`resetFields` will re-mount component under Field to clean up customize component side effects (like async data, cached state, etc.). It's by design.

View File

@ -340,6 +340,10 @@ validator(rule, value, callback) => {
当你为 Form.Item 设置 `name` 属性后,子组件会转为受控模式。因而 `defaultValue` 不会生效。你需要在 Form 上通过 `initialValues` 设置默认值。
### 为什么第一次调用 `ref` 的 From 为空?
`ref` 仅在节点被加载时才会被赋值,请参考 React 官方文档https://reactjs.org/docs/refs-and-the-dom.html#accessing-refs
### 为什么 `resetFields` 会重新 mount 组件?
`resetFields` 会重置整个 Field因而其子组件也会重新 mount 从而消除自定义组件可能存在的副作用(例如异步数据、状态等等)。

View File

@ -46,6 +46,7 @@ import jaJP from '../ja_JP';
import knIN from '../kn_IN';
import koKR from '../ko_KR';
import kmrIQ from '../kmr_IQ';
import kuIQ from '../ku_IQ';
import lvLV from '../lv_LV';
import ltLT from '../lt_LT';
import mkMK from '../mk_MK';
@ -103,6 +104,7 @@ const locales = [
knIN,
koKR,
kmrIQ,
kuIQ,
ltLT,
mkMK,
msMY,

View File

@ -1,3 +1,4 @@
import TestUtils from 'react-dom/test-utils';
import Modal from '..';
import { destroyFns } from '../Modal';
@ -140,6 +141,42 @@ describe('Modal.confirm triggers callbacks correctly', () => {
jest.useRealTimers();
});
it('should close confirm modal when click cancel button', () => {
jest.useFakeTimers();
const onCancel = jest.fn();
Modal.confirm({
title: 'title',
content: 'content',
onCancel,
});
jest.runAllTimers();
expect($$(`.ant-modal-confirm-confirm`)).toHaveLength(1);
$$('.ant-btn')[0].click();
jest.runAllTimers();
expect($$(`.ant-modal-confirm-confirm`)).toHaveLength(0);
expect(onCancel).toHaveBeenCalledTimes(1);
jest.useRealTimers();
});
it('should close confirm modal when press ESC', () => {
jest.useFakeTimers();
const onCancel = jest.fn();
Modal.confirm({
title: 'title',
content: 'content',
onCancel,
});
jest.runAllTimers();
expect($$(`.ant-modal-confirm-confirm`)).toHaveLength(1);
TestUtils.Simulate.keyDown($$('.ant-modal')[0], {
keyCode: 27,
});
jest.runAllTimers();
expect($$(`.ant-modal-confirm-confirm`)).toHaveLength(0);
expect(onCancel).toHaveBeenCalledTimes(1);
jest.useRealTimers();
});
it('should not close modals when click confirm button when onOk has argument', () => {
jest.useFakeTimers();
['info', 'success', 'warning', 'error'].forEach(type => {

View File

@ -735,7 +735,7 @@ exports[`renders ./components/select/demo/custom-dropdown-menu.md correctly 1`]
exports[`renders ./components/select/demo/custom-tag-render.md correctly 1`] = `
<div
class="ant-select ant-select-multiple ant-select-show-search"
class="ant-select ant-select-multiple ant-select-show-arrow ant-select-show-search"
style="width:100%"
>
<div
@ -825,6 +825,33 @@ exports[`renders ./components/select/demo/custom-tag-render.md correctly 1`] = `
</span>
</span>
</div>
<span
aria-hidden="true"
class="ant-select-arrow"
style="user-select:none;-webkit-user-select:none"
unselectable="on"
>
<span
aria-label="down"
class="anticon anticon-down ant-select-suffix"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
</div>
`;
@ -1091,7 +1118,7 @@ exports[`renders ./components/select/demo/label-in-value.md correctly 1`] = `
exports[`renders ./components/select/demo/multiple.md correctly 1`] = `
Array [
<div
class="ant-select ant-select-multiple ant-select-show-search"
class="ant-select ant-select-multiple ant-select-allow-clear ant-select-show-search"
style="width:100%"
>
<div
@ -1195,6 +1222,33 @@ Array [
</span>
</span>
</div>
<span
aria-hidden="true"
class="ant-select-clear"
style="user-select:none;-webkit-user-select:none"
unselectable="on"
>
<span
aria-label="close-circle"
class="anticon anticon-close-circle"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
</span>
</span>
</div>,
<br />,
<div

View File

@ -7,11 +7,11 @@ title:
## zh-CN
允许自定义选择标签的样式
允许自定义选择标签的样式
## en-US
Allows for custom rendering of tags
Allows for custom rendering of tags.
```jsx
import { Select, Tag } from 'antd';
@ -31,6 +31,7 @@ function tagRender(props) {
ReactDOM.render(
<Select
mode="multiple"
showArrow
tagRender={tagRender}
defaultValue={['gold', 'cyan']}
style={{ width: '100%' }}

View File

@ -31,6 +31,7 @@ ReactDOM.render(
<>
<Select
mode="multiple"
allowClear
style={{ width: '100%' }}
placeholder="Please select"
defaultValue={['a10', 'c12']}

View File

@ -7,11 +7,11 @@ title:
## zh-CN
tags select随意输入的内容scroll the menu
tags select随意输入的内容scroll the menu
## en-US
Select with tags, transform input to tag (scroll the menu)
Select with tags, transform input to tag (scroll the menu).
```jsx
import { Select } from 'antd';

View File

@ -27,7 +27,10 @@ Select component to select value from options.
| allowClear | Show clear button | boolean | false | |
| autoClearSearchValue | Whether the current search will be cleared on selecting an item. Only applies when `mode` is set to `multiple` or `tags` | boolean | true | |
| autoFocus | Get focus by default | boolean | false | |
| bordered | Whether has border style | boolean | true | |
| clearIcon | The custom clear icon | ReactNode | - | |
| defaultActiveFirstOption | Whether active first option by default | boolean | true | |
| defaultOpen | Initial open state of dropdown | boolean | - | |
| defaultValue | Initial selected option | string \| string\[]<br />number \| number\[]<br />LabeledValue \| LabeledValue[] | - | |
| disabled | Whether disabled select | boolean | false | |
| dropdownClassName | The className of dropdown menu | string | - | |
@ -36,32 +39,20 @@ Select component to select value from options.
| dropdownStyle | The style of dropdown menu | CSSProperties | - | |
| filterOption | If true, filter options by input, if function, filter options against it. The function will receive two arguments, `inputValue` and `option`, if the function returns `true`, the option will be included in the filtered set; Otherwise, it will be excluded | boolean \| function(inputValue, option) | true | |
| getPopupContainer | Parent Node which the selector should be rendered to. Default to `body`. When position issues happen, try to modify it into scrollable content and position it relative. [Example](https://codesandbox.io/s/4j168r7jw0) | function(triggerNode) | () => document.body | |
| labelInValue | Whether to embed label in value, turn the format of value from `string` to `{ value: string, label: ReactNode }` | boolean | false | |
| labelInValue | Whether to embed label in value, turn the format of value from `string` to { value: string, label: ReactNode } | boolean | false | |
| listHeight | Config popup height | number | 256 | |
| loading | Indicate loading state | boolean | false | |
| maxTagCount | Max tag count to show | number | - | |
| maxTagTextLength | Max tag text length to show | number | - | |
| maxTagPlaceholder | Placeholder for not showing tags | ReactNode \| function(omittedValues) | - | |
| tagRender | Customize tag render | (props) => ReactNode | - | |
| maxTagTextLength | Max tag text length to show | number | - | |
| menuItemSelectedIcon | The custom menuItemSelected icon with multiple options | ReactNode | - | |
| mode | Set mode of Select | `multiple` \| `tags` | - | |
| notFoundContent | Specify content to show when no result matches | ReactNode | `Not Found` | |
| options | Select options. Will get better perf than jsx definition | { label, value }[] | - | |
| optionFilterProp | Which prop value of option will be used for filter if filterOption is true | string | `value` | |
| optionLabelProp | Which prop value of option will render as content of select. [Example](https://codesandbox.io/s/antd-reproduction-template-tk678) | string | `children` | |
| placeholder | Placeholder of select | string \| ReactNode | - | |
| showArrow | Whether to show the drop-down arrow | boolean | true(for single select), false(for multiple select) | |
| showSearch | Whether show search input in single mode | boolean | false | |
| size | Size of Select input | `large` \| `middle` \| `small` | - | |
| suffixIcon | The custom suffix icon | ReactNode | - | |
| removeIcon | The custom remove icon | ReactNode | - | |
| clearIcon | The custom clear icon | ReactNode | - | |
| menuItemSelectedIcon | The custom menuItemSelected icon with multiple options | ReactNode | - | |
| tokenSeparators | Separator used to tokenize on tag \| multiple mode | string\[] | - | |
| value | Current selected option | string \| string\[]<br />number \| number\[]<br />LabeledValue \| LabeledValue[] | - | |
| virtual | Disable virtual scroll when set to false | boolean | true | 4.1.0 |
| onBlur | Called when blur | function | - | |
| onChange | Called when select an option or input value change | function(value, option:Option \| Array&lt;Option>) | - | |
| onClear | Called when clear | function | - | 4.6.0 |
| onDeselect | Called when a option is deselected, param is the selected option's value. Only called for multiple or tags, effective in multiple or tags mode only | function(string \| number \| LabeledValue) | - | |
| onDeselect | Called when a option is deselected, param is the selected option's value. Only called for `multiple` or `tags`, effective in multiple or tags mode only | function(string \| number \| LabeledValue) | - | |
| onDropdownVisibleChange | Called when dropdown open | function(open) | - | |
| onFocus | Called when focus | function | - | |
| onInputKeyDown | Called when key pressed | function | - | |
| onMouseEnter | Called when mouse enter | function | - | |
@ -69,11 +60,22 @@ Select component to select value from options.
| onPopupScroll | Called when dropdown scrolls | function | - | |
| onSearch | Callback function that is fired when input changed | function(value: string) | - | |
| onSelect | Called when a option is selected, the params are option's value (or key) and option instance | function(string \| number \| LabeledValue, option: Option) | - | |
| defaultOpen | Initial open state of dropdown | boolean | - | |
| open | Controlled open state of dropdown | boolean | - | |
| onDropdownVisibleChange | Call when dropdown open | function(open) | - | |
| loading | Indicate loading state | boolean | false | |
| bordered | Whether has border style | boolean | true | |
| options | Select options. Will get better perf than jsx definition | { label, value }[] | - | |
| optionFilterProp | Which prop value of option will be used for filter if filterOption is true | string | `value` | |
| optionLabelProp | Which prop value of option will render as content of select. [Example](https://codesandbox.io/s/antd-reproduction-template-tk678) | string | `children` | |
| placeholder | Placeholder of select | string \| ReactNode | - | |
| removeIcon | The custom remove icon | ReactNode | - | |
| showArrow | Whether to show the drop-down arrow | boolean | true(for single select), false(for multiple select) | |
| showSearch | Whether show search input in single mode | boolean | false | |
| size | Size of Select input | `large` \| `middle` \| `small` | - | |
| suffixIcon | The custom suffix icon | ReactNode | - | |
| tagRender | Customize tag render | (props) => ReactNode | - | |
| tokenSeparators | Separator used to tokenize on `tag` and `multiple` mode | string\[] | - | |
| value | Current selected option | string \| string\[]<br />number \| number\[]<br />LabeledValue \| LabeledValue[] | - | |
| virtual | Disable virtual scroll when set to false | boolean | true | 4.1.0 |
> Note, if you find that the drop-down menu scrolls with the page, or you need to trigger Select in other popup layers, please try to use `getPopupContainer={triggerNode => triggerNode.parentElement}` to fix the drop-down popup rendering node in the parent element of the trigger .
### Select Methods
@ -86,10 +88,10 @@ Select component to select value from options.
| Property | Description | Type | Default | Version |
| --------- | ------------------------------------------ | ---------------- | ------- | ------- |
| className | The additional class to option | string | - | |
| disabled | Disable this option | boolean | false | |
| title | `title` of Select after select this Option | string | - | |
| value | Default to filter with this property | string \| number | - | |
| className | The additional class to option | string | - | |
### OptGroup props

View File

@ -28,7 +28,10 @@ cover: https://gw.alipayobjects.com/zos/alicdn/_0XzgOis7/Select.svg
| allowClear | 支持清除 | boolean | false | |
| autoClearSearchValue | 是否在选中项后清空搜索框,只在 `mode``multiple``tags` 时有效 | boolean | true | |
| autoFocus | 默认获取焦点 | boolean | false | |
| bordered | 是否有边框 | boolean | true | |
| clearIcon | 自定义的多选框清空图标 | ReactNode | - | |
| defaultActiveFirstOption | 是否默认高亮第一个选项 | boolean | true | |
| defaultOpen | 是否默认展开下拉菜单 | boolean | - | |
| defaultValue | 指定默认选中的条目 | string \| string\[]<br />number \| number\[]<br />LabeledValue \| LabeledValue[] | - | |
| disabled | 是否禁用 | boolean | false | |
| dropdownClassName | 下拉菜单的 className 属性 | string | - | |
@ -37,32 +40,20 @@ cover: https://gw.alipayobjects.com/zos/alicdn/_0XzgOis7/Select.svg
| dropdownStyle | 下拉菜单的 style 属性 | CSSProperties | - | |
| filterOption | 是否根据输入项进行筛选。当其为一个函数时,会接收 `inputValue` `option` 两个参数,当 `option` 符合筛选条件时,应返回 true反之则返回 false | boolean \| function(inputValue, option) | true | |
| getPopupContainer | 菜单渲染父节点。默认渲染到 body 上,如果你遇到菜单滚动定位问题,试试修改为滚动的区域,并相对其定位。[示例](https://codesandbox.io/s/4j168r7jw0) | function(triggerNode) | () => document.body | |
| labelInValue | 是否把每个选项的 label 包装到 value 中,会把 Select 的 value 类型从 `string` 变为 `{ value: string, label: ReactNode }` 的格式 | boolean | false | |
| labelInValue | 是否把每个选项的 label 包装到 value 中,会把 Select 的 value 类型从 `string` 变为 { value: string, label: ReactNode } 的格式 | boolean | false | |
| listHeight | 设置弹窗滚动高度 | number | 256 | |
| loading | 加载中状态 | boolean | false | |
| maxTagCount | 最多显示多少个 tag | number | - | |
| maxTagTextLength | 最大显示的 tag 文本长度 | number | - | |
| maxTagPlaceholder | 隐藏 tag 时显示的内容 | ReactNode \| function(omittedValues) | - | |
| tagRender | 自定义 tag 内容 render | (props) => ReactNode | - | |
| maxTagTextLength | 最大显示的 tag 文本长度 | number | - | |
| menuItemSelectedIcon | 自定义多选时当前选中的条目图标 | ReactNode | - | |
| mode | 设置 Select 的模式为多选或标签 | `multiple` \| `tags` | - | |
| notFoundContent | 当下拉列表为空时显示的内容 | ReactNode | `Not Found` | |
| options | 数据化配置选项内容,相比 jsx 定义会获得更好的渲染性能 | { label, value }[] | - | |
| optionFilterProp | 搜索时过滤对应的 option 属性,如设置为 children 表示对内嵌内容进行搜索。[示例](https://codesandbox.io/s/antd-reproduction-template-tk678) | string | `value` | |
| optionLabelProp | 回填到选择框的 Option 的属性值,默认是 Option 的子元素。比如在子元素需要高亮效果时,此值可以设为 `value` | string | `children` | |
| placeholder | 选择框默认文字 | string | - | |
| showArrow | 是否显示下拉小箭头 | boolean | 单选为 true多选为 false | |
| showSearch | 使单选模式可搜索 | boolean | false | |
| size | 选择框大小 | `large` \| `middle` \| `small` | - | |
| suffixIcon | 自定义的选择框后缀图标 | ReactNode | - | |
| removeIcon | 自定义的多选框清除图标 | ReactNode | - | |
| clearIcon | 自定义的多选框清空图标 | ReactNode | - | |
| menuItemSelectedIcon | 自定义多选时当前选中的条目图标 | ReactNode | - | |
| tokenSeparators | 在 tags 和 multiple 模式下自动分词的分隔符 | string\[] | - | |
| value | 指定当前选中的条目 | string \| string\[]<br />number \| number\[]<br />LabeledValue \| LabeledValue[] | - | |
| virtual | 设置 false 时关闭虚拟滚动 | boolean | true | 4.1.0 |
| onBlur | 失去焦点时回调 | function | - | |
| onChange | 选中 option或 input 的 value 变化时,调用此函数 | function(value, option:Option \| Array&lt;Option>) | - | |
| onClear | 清除内容时回调 | function | - | 4.6.0 |
| onDeselect | 取消选中时调用,参数为选中项的 value (或 key) 值,仅在 multiple 或 tags 模式下生效 | function(string \| number \| LabeledValue) | - | |
| onDeselect | 取消选中时调用,参数为选中项的 value (或 key) 值,仅在 `multiple``tags` 模式下生效 | function(string \| number \| LabeledValue) | - | |
| onDropdownVisibleChange | 展开下拉菜单的回调 | function(open) | - | |
| onFocus | 获得焦点时回调 | function | - | |
| onInputKeyDown | 按键按下时回调 | function | - | |
| onMouseEnter | 鼠标移入时回调 | function | - | |
@ -70,11 +61,20 @@ cover: https://gw.alipayobjects.com/zos/alicdn/_0XzgOis7/Select.svg
| onPopupScroll | 下拉列表滚动时的回调 | function | - | |
| onSearch | 文本框值变化时回调 | function(value: string) | - | |
| onSelect | 被选中时调用,参数为选中项的 value (或 key) 值 | function(string \| number \| LabeledValue, option: Option) | - | |
| defaultOpen | 是否默认展开下拉菜单 | boolean | - | |
| open | 是否展开下拉菜单 | boolean | - | |
| onDropdownVisibleChange | 展开下拉菜单的回调 | function(open) | - | |
| loading | 加载中状态 | boolean | false | |
| bordered | 是否有边框 | boolean | true | |
| options | 数据化配置选项内容,相比 jsx 定义会获得更好的渲染性能 | { label, value }[] | - | |
| optionFilterProp | 搜索时过滤对应的 option 属性,如设置为 children 表示对内嵌内容进行搜索。[示例](https://codesandbox.io/s/antd-reproduction-template-tk678) | string | `value` | |
| optionLabelProp | 回填到选择框的 Option 的属性值,默认是 Option 的子元素。比如在子元素需要高亮效果时,此值可以设为 `value` | string | `children` | |
| placeholder | 选择框默认文字 | string | - | |
| removeIcon | 自定义的多选框清除图标 | ReactNode | - | |
| showArrow | 是否显示下拉小箭头 | boolean | 单选为 true多选为 false | |
| showSearch | 使单选模式可搜索 | boolean | false | |
| size | 选择框大小 | `large` \| `middle` \| `small` | - | |
| suffixIcon | 自定义的选择框后缀图标 | ReactNode | - | |
| tagRender | 自定义 tag 内容 render | (props) => ReactNode | - | |
| tokenSeparators | 在 `tags``multiple` 模式下自动分词的分隔符 | string\[] | - | |
| value | 指定当前选中的条目 | string \| string\[]<br />number \| number\[]<br />LabeledValue \| LabeledValue[] | - | |
| virtual | 设置 false 时关闭虚拟滚动 | boolean | true | 4.1.0 |
> 注意,如果发现下拉菜单跟随页面滚动,或者需要在其他弹层中触发 Select请尝试使用 `getPopupContainer={triggerNode => triggerNode.parentElement}` 将下拉弹层渲染节点固定在触发器的父元素中。
@ -89,10 +89,10 @@ cover: https://gw.alipayobjects.com/zos/alicdn/_0XzgOis7/Select.svg
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --------- | --------------------------------- | ---------------- | ------ | ---- |
| className | Option 器类名 | string | - | |
| disabled | 是否禁用 | boolean | false | |
| title | 选中该 Option 后Select 的 title | string | - | |
| value | 默认根据此属性值进行筛选 | string \| number | - | |
| className | Option 器类名 | string | - | |
### OptGroup props

49
components/space/Item.tsx Normal file
View File

@ -0,0 +1,49 @@
import * as React from 'react';
import { LastIndexContext } from '.';
import { SizeType } from '../config-provider/SizeContext';
const spaceSize = {
small: 8,
middle: 16,
large: 24,
};
export interface ItemProps {
className: string;
children: React.ReactNode;
index: number;
direction?: 'horizontal' | 'vertical';
size?: SizeType | number;
marginDirection: 'marginLeft' | 'marginRight';
}
export default function Item({
className,
direction,
index,
size,
marginDirection,
children,
}: ItemProps) {
const latestIndex = React.useContext(LastIndexContext);
if (children === null || children === undefined) {
return null;
}
return (
<div
className={className}
style={
index >= latestIndex
? {}
: {
[direction === 'vertical' ? 'marginBottom' : marginDirection]:
typeof size === 'string' ? spaceSize[size] : size,
}
}
>
{children}
</div>
);
}

View File

@ -395,12 +395,6 @@ exports[`renders ./components/space/demo/debug.md correctly 1`] = `
</button>
</span>
</div>
<div
class="ant-space-item"
/>
<div
class="ant-space-item"
/>
<div
class="ant-space-item"
style="margin-right:8px"

View File

@ -43,8 +43,8 @@ describe('Space', () => {
</Space>,
);
expect(wrapper.find('.ant-space-item').at(0).prop('style').marginRight).toBe(10);
expect(wrapper.find('.ant-space-item').at(1).prop('style').marginRight).toBeUndefined();
expect(wrapper.find('div.ant-space-item').at(0).prop('style').marginRight).toBe(10);
expect(wrapper.find('div.ant-space-item').at(1).prop('style').marginRight).toBeUndefined();
});
it('should render vertical space width customize size', () => {
@ -55,8 +55,8 @@ describe('Space', () => {
</Space>,
);
expect(wrapper.find('.ant-space-item').at(0).prop('style').marginBottom).toBe(10);
expect(wrapper.find('.ant-space-item').at(1).prop('style').marginBottom).toBeUndefined();
expect(wrapper.find('div.ant-space-item').at(0).prop('style').marginBottom).toBe(10);
expect(wrapper.find('div.ant-space-item').at(1).prop('style').marginBottom).toBeUndefined();
});
it('should render correct with children', () => {
@ -78,7 +78,7 @@ describe('Space', () => {
</Space>,
);
expect(wrapper.find('.ant-space-item').length).toBe(3);
expect(wrapper.find('div.ant-space-item').length).toBe(3);
});
it('should be keep store', () => {

View File

@ -32,6 +32,8 @@ ReactDOM.render(
{false}
{1}
Button
{null}
{undefined}
</Space>,
mountNode,
);

View File

@ -2,6 +2,9 @@ import * as React from 'react';
import classNames from 'classnames';
import { ConfigConsumerProps, ConfigContext } from '../config-provider';
import { SizeType } from '../config-provider/SizeContext';
import Item from './Item';
export const LastIndexContext = React.createContext(0);
export interface SpaceProps {
prefixCls?: string;
@ -13,12 +16,6 @@ export interface SpaceProps {
align?: 'start' | 'end' | 'center' | 'baseline';
}
const spaceSize = {
small: 8,
middle: 16,
large: 24,
};
const Space: React.FC<SpaceProps> = props => {
const { getPrefixCls, space, direction: directionConfig }: ConfigConsumerProps = React.useContext(
ConfigContext,
@ -56,25 +53,32 @@ const Space: React.FC<SpaceProps> = props => {
const marginDirection = directionConfig === 'rtl' ? 'marginLeft' : 'marginRight';
// Calculate latest one
let latestIndex = 0;
const nodes = React.Children.map(children, (child, i) => {
if (child !== null && child !== undefined) {
latestIndex = i;
}
/* eslint-disable react/no-array-index-key */
return (
<Item
className={itemClassName}
key={`${itemClassName}-${i}`}
direction={direction}
size={size}
index={i}
marginDirection={marginDirection}
>
{child}
</Item>
);
/* eslint-enable */
});
return (
<div className={cn} {...otherProps}>
{React.Children.map(children, (child, i) => (
<div
className={itemClassName}
// eslint-disable-next-line react/no-array-index-key
key={`${itemClassName}-${i}`}
style={
i === len - 1 || child === null || child === undefined
? {}
: {
[direction === 'vertical' ? 'marginBottom' : marginDirection]:
typeof size === 'string' ? spaceSize[size] : size,
}
}
>
{child}
</div>
))}
<LastIndexContext.Provider value={latestIndex}>{nodes}</LastIndexContext.Provider>
</div>
);
};

View File

@ -172,6 +172,7 @@ Properties for expandable.
| defaultExpandedRowKeys | Initial expanded row keys | string\[] | - |
| expandIcon | Customize row expand Icon. Ref [example](https://codesandbox.io/s/fervent-bird-nuzpr) | function(props): ReactNode | - |
| expandIconColumnIndex | Customize expand icon column index. Not render when `-1` | number | - |
| expandedRowClassName | Expanded row's className | function(record, index, indent): string | - |
| expandedRowKeys | Current expanded row keys | string\[] | - |
| expandedRowRender | Expanded container render for each row | function(record, index, indent, expanded): ReactNode | - |
| expandRowByClick | Whether to expand row by clicking anywhere in the whole row | boolean | false |

View File

@ -179,6 +179,7 @@ const columns = [
| defaultExpandedRowKeys | 默认展开的行 | string\[] | - |
| expandIcon | 自定义展开图标,参考[示例](https://codesandbox.io/s/fervent-bird-nuzpr) | function(props): ReactNode | - |
| expandIconColumnIndex | 自定义展开按钮的列顺序,`-1` 时不展示 | number | - |
| expandedRowClassName | 展开行的 className | function(record, index, indent): string | - |
| expandedRowKeys | 展开的行,控制属性 | string\[] | - |
| expandedRowRender | 额外的展开行 | function(record, index, indent, expanded): ReactNode | - |
| expandRowByClick | 通过点击行来展开子行 | boolean | false |

View File

@ -412,12 +412,13 @@
float: left;
box-sizing: border-box;
width: ceil(@font-size-sm * 1.4);
height: ceil(@font-size-sm * 1.4);
width: ceil((@font-size-sm * 1.4 - @border-width-base * 3) / 2) * 2 + @border-width-base * 3;
height: ceil((@font-size-sm * 1.4 - @border-width-base * 3) / 2) * 2 + @border-width-base * 3;
padding: 0;
color: inherit;
line-height: @font-size-sm;
vertical-align: floor((@font-size-base - ceil(@font-size-sm * 1.4)) / 2);
line-height: ceil((@font-size-sm * 1.4 - @border-width-base * 3) / 2) * 2 + @border-width-base *
3;
// vertical-align: floor((@font-size-base - ceil(@font-size-sm * 1.4)) / 2);
background: @table-expand-icon-bg;
border: @border-width-base @border-style-base @border-color-split;
border-radius: @border-radius-base;
@ -440,7 +441,7 @@
}
&::before {
top: 7px;
top: ceil((@font-size-sm * 1.4 - @border-width-base * 3) / 2);
right: 3px;
left: 3px;
height: @border-width-base;
@ -449,7 +450,7 @@
&::after {
top: 3px;
bottom: 3px;
left: 7px;
left: ceil((@font-size-sm * 1.4 - @border-width-base * 3) / 2);
width: @border-width-base;
transform: rotate(90deg);
}
@ -474,7 +475,8 @@
}
.@{table-prefix-cls}-row-indent + & {
margin-top: (@font-size-base * @line-height-base - ceil(@font-size-sm * 1.4)) / 2;
margin-top: (@font-size-base * @line-height-base - @border-width-base * 3) / 2 -
ceil((@font-size-sm * 1.4 - @border-width-base * 3) / 2);
margin-right: @padding-xs;
}
}

View File

@ -30,16 +30,16 @@ const OperationsSlot = {
const options = ['left', 'right'];
const Demo = () => {
const [positon, setPosition] = React.useState(['left', 'right']);
const [position, setPosition] = React.useState(['left', 'right']);
const slot = React.useMemo(() => {
if (positon.length === 0) return null;
if (position.length === 0) return null;
return positon.reduce(
(acc, direaction) => ({ ...acc, [direaction]: OperationsSlot[direaction] }),
return position.reduce(
(acc, direction) => ({ ...acc, [direction]: OperationsSlot[direction] }),
{},
);
}, [positon]);
}, [position]);
return (
<>
@ -61,7 +61,7 @@ const Demo = () => {
<Divider />
<CheckboxGroup
options={options}
value={positon}
value={position}
onChange={value => {
setPosition(value);
}}

View File

@ -45,4 +45,9 @@ describe('TreeSelect', () => {
expect(wrapper.render()).toMatchSnapshot();
});
});
it('should support notFoundContent', () => {
const wrapper = mount(<TreeSelect treeIcon open notFoundContent="notFoundContent" />);
expect(wrapper.text()).toBe('notFoundContent');
});
});

View File

@ -35,12 +35,10 @@
}
}
// ========================== Tree ==========================
.antTreeFn(@select-tree-prefix-cls);
// change switcher icon rotation in rtl direction
.@{select-tree-prefix-cls} {
// >>> Switcher
.antTreeFn(@select-tree-prefix-cls);
// change switcher icon rotation in rtl direction
& &-switcher {
&_close {
.@{select-tree-prefix-cls}-switcher-icon {

View File

@ -9,6 +9,8 @@
.antCheckboxFn(@checkbox-prefix-cls: ~'@{ant-prefix}-tree-checkbox');
.antTreeFn(@tree-prefix-cls);
.@{tree-prefix-cls} {
.antTreeFn(@tree-prefix-cls);
}
@import './rtl';

View File

@ -31,233 +31,231 @@
.antTreeFn(@custom-tree-prefix-cls) {
@custom-tree-node-prefix-cls: ~'@{custom-tree-prefix-cls}-treenode';
.@{custom-tree-prefix-cls} {
.reset-component;
background: @tree-bg;
.reset-component;
background: @tree-bg;
border-radius: @border-radius-base;
transition: background-color 0.3s;
&-focused:not(:hover):not(&-active-focused) {
background: @primary-1;
}
// =================== Virtual List ===================
&-list-holder-inner {
align-items: flex-start;
}
&.@{custom-tree-prefix-cls}-block-node {
.@{custom-tree-prefix-cls}-list-holder-inner {
align-items: stretch;
// >>> Title
.@{custom-tree-prefix-cls}-node-content-wrapper {
flex: auto;
}
}
}
// ===================== TreeNode =====================
.@{custom-tree-node-prefix-cls} {
display: flex;
align-items: flex-start;
padding: 0 0 (@padding-xs / 2) 0;
outline: none;
// Disabled
&-disabled {
// >>> Title
.@{custom-tree-prefix-cls}-node-content-wrapper {
color: @disabled-color;
cursor: not-allowed;
&:hover {
background: transparent;
}
}
}
&-active .@{custom-tree-prefix-cls}-node-content-wrapper {
background: @tree-node-hover-bg;
}
}
// >>> Indent
&-indent {
align-self: stretch;
white-space: nowrap;
user-select: none;
&-unit {
display: inline-block;
width: @tree-title-height;
}
}
// >>> Switcher
& &-switcher {
.antTreeSwitcherIcon();
flex: none;
width: @tree-title-height;
height: @tree-title-height;
margin: 0;
line-height: @tree-title-height;
text-align: center;
cursor: pointer;
&-noop {
cursor: default;
}
&_close {
.@{custom-tree-prefix-cls}-switcher-icon {
svg {
transform: rotate(-90deg);
}
}
}
&-loading-icon {
color: @primary-color;
}
&-leaf-line {
z-index: 1;
display: inline-block;
width: 100%;
height: 100%;
&::before {
position: absolute;
height: @tree-title-height;
margin-left: -1px;
border-left: 1px solid @normal-color;
content: ' ';
}
&::after {
position: absolute;
width: @tree-title-height - 14px;
height: @tree-title-height - 10px;
margin-left: -1px;
border-bottom: 1px solid @normal-color;
content: ' ';
}
}
}
// >>> Checkbox
& &-checkbox {
top: initial;
margin: ((@tree-title-height - @checkbox-size) / 2) 8px 0 0;
}
// >>> Title
& &-node-content-wrapper {
min-height: @tree-title-height;
margin: 0;
padding: 0 4px;
color: inherit;
line-height: @tree-title-height;
background: transparent;
border-radius: @border-radius-base;
transition: background-color 0.3s;
cursor: pointer;
transition: all 0.3s, border 0s, line-height 0s;
&-focused:not(:hover):not(&-active-focused) {
background: @primary-1;
&:hover {
background-color: @tree-node-hover-bg;
}
// =================== Virtual List ===================
&-list-holder-inner {
align-items: flex-start;
&.@{custom-tree-prefix-cls}-node-selected {
background-color: @tree-node-selected-bg;
}
&.@{custom-tree-prefix-cls}-block-node {
.@{custom-tree-prefix-cls}-list-holder-inner {
align-items: stretch;
// >>> Title
.@{custom-tree-prefix-cls}-node-content-wrapper {
flex: auto;
}
}
}
// ===================== TreeNode =====================
.@{custom-tree-node-prefix-cls} {
display: flex;
align-items: flex-start;
padding: 0 0 (@padding-xs / 2) 0;
outline: none;
// Disabled
&-disabled {
// >>> Title
.@{custom-tree-prefix-cls}-node-content-wrapper {
color: @disabled-color;
cursor: not-allowed;
&:hover {
background: transparent;
}
}
}
&-active .@{custom-tree-prefix-cls}-node-content-wrapper {
background: @tree-node-hover-bg;
}
}
// >>> Indent
&-indent {
align-self: stretch;
white-space: nowrap;
user-select: none;
&-unit {
display: inline-block;
width: @tree-title-height;
}
}
// >>> Switcher
& &-switcher {
.antTreeSwitcherIcon();
flex: none;
// Icon
.@{custom-tree-prefix-cls}-iconEle {
display: inline-block;
width: @tree-title-height;
height: @tree-title-height;
margin: 0;
line-height: @tree-title-height;
text-align: center;
cursor: pointer;
&-noop {
cursor: default;
vertical-align: top;
&:empty {
display: none;
}
}
}
&_close {
.@{custom-tree-prefix-cls}-switcher-icon {
svg {
transform: rotate(-90deg);
}
}
}
// ==================== Draggable =====================
&-node-content-wrapper[draggable='true'] {
line-height: @tree-title-height - 4px;
border-top: 2px transparent solid;
border-bottom: 2px transparent solid;
user-select: none;
}
&-loading-icon {
color: @primary-color;
}
.@{custom-tree-node-prefix-cls}.drag-over {
> [draggable] {
color: white;
background-color: @primary-color;
opacity: 0.8;
}
}
.@{custom-tree-node-prefix-cls}.drag-over-gap-top {
> [draggable] {
border-top-color: @primary-color;
}
}
.@{custom-tree-node-prefix-cls}.drag-over-gap-bottom {
> [draggable] {
border-bottom-color: @primary-color;
}
}
&-leaf-line {
z-index: 1;
display: inline-block;
width: 100%;
// ==================== Show Line =====================
&-show-line {
// ================ Indent lines ================
.@{custom-tree-prefix-cls}-indent {
&-unit {
position: relative;
height: 100%;
&:first-child::after {
position: absolute;
top: calc(100% - @tree-title-height - 4px);
right: @tree-title-height / 2;
bottom: -4px;
border-right: 1px solid @border-color-base;
content: '';
}
&::before {
position: absolute;
height: @tree-title-height;
margin-left: -1px;
border-left: 1px solid @normal-color;
content: ' ';
top: calc(100% - 4px);
right: -@tree-title-height / 2;
bottom: -@tree-title-height - 4px;
border-right: 1px solid @border-color-base;
content: '';
}
&::after {
position: absolute;
width: @tree-title-height - 14px;
height: @tree-title-height - 10px;
margin-left: -1px;
border-bottom: 1px solid @normal-color;
content: ' ';
}
}
}
// >>> Checkbox
& &-checkbox {
top: initial;
margin: ((@tree-title-height - @checkbox-size) / 2) 8px 0 0;
}
// >>> Title
& &-node-content-wrapper {
min-height: @tree-title-height;
margin: 0;
padding: 0 4px;
color: inherit;
line-height: @tree-title-height;
background: transparent;
border-radius: @border-radius-base;
cursor: pointer;
transition: all 0.3s;
&:hover {
background-color: @tree-node-hover-bg;
}
&.@{custom-tree-prefix-cls}-node-selected {
background-color: @tree-node-selected-bg;
}
// Icon
.@{custom-tree-prefix-cls}-iconEle {
display: inline-block;
width: @tree-title-height;
height: @tree-title-height;
line-height: @tree-title-height;
text-align: center;
vertical-align: top;
&:empty {
&-end::before,
&-end-first-level::after {
display: none;
}
}
}
// ==================== Draggable =====================
&-node-content-wrapper[draggable='true'] {
line-height: @tree-title-height - 4px;
border-top: 2px transparent solid;
border-bottom: 2px transparent solid;
user-select: none;
}
.@{custom-tree-node-prefix-cls}.drag-over {
> [draggable] {
color: white;
background-color: @primary-color;
opacity: 0.8;
}
}
.@{custom-tree-node-prefix-cls}.drag-over-gap-top {
> [draggable] {
border-top-color: @primary-color;
}
}
.@{custom-tree-node-prefix-cls}.drag-over-gap-bottom {
> [draggable] {
border-bottom-color: @primary-color;
}
}
// ==================== Show Line =====================
&-show-line {
// ================ Indent lines ================
.@{custom-tree-prefix-cls}-indent {
&-unit {
position: relative;
height: 100%;
&:first-child::after {
position: absolute;
top: calc(100% - @tree-title-height - 4px);
right: @tree-title-height / 2;
bottom: -4px;
border-right: 1px solid @border-color-base;
content: '';
}
&::before {
position: absolute;
top: calc(100% - 4px);
right: -@tree-title-height / 2;
bottom: -@tree-title-height - 4px;
border-right: 1px solid @border-color-base;
content: '';
}
&-end::before,
&-end-first-level::after {
display: none;
}
/* Motion should hide line of measure */
.@{custom-tree-node-prefix-cls}-motion:not(.@{tree-motion}-leave):not(.@{tree-motion}-appear-active) {
.@{custom-tree-prefix-cls}-indent-unit {
&::after,
&::before {
display: none;
}
}
}
/* Motion should hide line of measure */
.@{custom-tree-node-prefix-cls}-motion:not(.@{tree-motion}-leave):not(.@{tree-motion}-appear-active) {
.@{custom-tree-prefix-cls}-indent-unit {
&::after,
&::before {
display: none;
}
}
}
// ============== Cover Background ==============
.@{custom-tree-prefix-cls}-switcher {
z-index: 1;
background: @component-background;
}
// ============== Cover Background ==============
.@{custom-tree-prefix-cls}-switcher {
z-index: 1;
background: @component-background;
}
}
}

View File

@ -5,6 +5,7 @@ import Dragger from './Dragger';
import UploadList from './UploadList';
import {
RcFile,
ShowUploadListInterface,
UploadProps,
UploadFile,
UploadLocale,
@ -217,35 +218,6 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
forceUpdate,
}));
const renderUploadList = (locale: UploadLocale) => {
const {
showRemoveIcon,
showPreviewIcon,
showDownloadIcon,
removeIcon,
downloadIcon,
} = showUploadList as any;
return (
<UploadList
listType={listType}
items={getFileList()}
previewFile={previewFile}
onPreview={onPreview}
onDownload={onDownload}
onRemove={handleRemove}
showRemoveIcon={!disabled && showRemoveIcon}
showPreviewIcon={showPreviewIcon}
showDownloadIcon={showDownloadIcon}
removeIcon={removeIcon}
downloadIcon={downloadIcon}
iconRender={iconRender}
locale={{ ...locale, ...propLocale }}
isImageUrl={isImageUrl}
progress={progress}
/>
);
};
const { getPrefixCls, direction } = React.useContext(ConfigContext);
const prefixCls = getPrefixCls('upload', customizePrefixCls);
@ -271,11 +243,37 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
delete rcUploadProps.id;
}
const uploadList = showUploadList ? (
<LocaleReceiver componentName="Upload" defaultLocale={defaultLocale.Upload}>
{renderUploadList}
</LocaleReceiver>
) : null;
const renderUploadList = (button?: React.ReactNode) =>
showUploadList ? (
<LocaleReceiver componentName="Upload" defaultLocale={defaultLocale.Upload}>
{(locale: UploadLocale) => {
const { showRemoveIcon, showPreviewIcon, showDownloadIcon, removeIcon, downloadIcon } =
typeof showUploadList === 'boolean' ? ({} as ShowUploadListInterface) : showUploadList;
return (
<UploadList
listType={listType}
items={getFileList()}
previewFile={previewFile}
onPreview={onPreview}
onDownload={onDownload}
onRemove={handleRemove}
showRemoveIcon={!disabled && showRemoveIcon}
showPreviewIcon={showPreviewIcon}
showDownloadIcon={showDownloadIcon}
removeIcon={removeIcon}
downloadIcon={downloadIcon}
iconRender={iconRender}
locale={{ ...locale, ...propLocale }}
isImageUrl={isImageUrl}
progress={progress}
appendAction={button}
/>
);
}}
</LocaleReceiver>
) : (
button
);
if (type === 'drag') {
const dragCls = classNames(
@ -302,7 +300,7 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
<div className={`${prefixCls}-drag-container`}>{children}</div>
</RcUpload>
</div>
{uploadList}
{renderUploadList()}
</span>
);
}
@ -323,8 +321,7 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
if (listType === 'picture-card') {
return (
<span className={classNames(className, `${prefixCls}-picture-card-wrapper`)}>
{uploadList}
{uploadButton}
{renderUploadList(uploadButton)}
</span>
);
}
@ -332,7 +329,7 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
return (
<span className={className}>
{uploadButton}
{uploadList}
{renderUploadList()}
</span>
);
};

View File

@ -36,6 +36,7 @@ const InternalUploadList: React.ForwardRefRenderFunction<unknown, UploadListProp
removeIcon: customRemoveIcon,
downloadIcon: customDownloadIcon,
progress: progressProps,
appendAction,
},
ref,
) => {
@ -334,13 +335,13 @@ const InternalUploadList: React.ForwardRefRenderFunction<unknown, UploadListProp
[`${prefixCls}-list-rtl`]: direction === 'rtl',
});
const animationDirection = listType === 'picture-card' ? 'animate-inline' : 'animate';
const transitionName = list.length === 0 ? '' : `${prefixCls}-${animationDirection}`;
return (
<Animate
transitionName={`${prefixCls}-${animationDirection}`}
component="div"
className={listClassNames}
>
<Animate transitionName={transitionName} component="div" className={listClassNames}>
{list}
{React.isValidElement(appendAction)
? React.cloneElement(appendAction, { key: 'appendAction' })
: appendAction}
</Animate>
);
};

File diff suppressed because it is too large Load Diff

View File

@ -10,4 +10,21 @@ describe('Upload.typescript', () => {
);
expect(upload).toBeTruthy();
});
it('showUploadList', () => {
const upload = (
<Upload
showUploadList={{
showPreviewIcon: true,
showRemoveIcon: true,
showDownloadIcon: true,
removeIcon: 'Remove',
downloadIcon: 'Download',
}}
>
<span>click to upload</span>
</Upload>
);
expect(upload).toBeTruthy();
});
});

View File

@ -880,4 +880,27 @@ describe('Upload List', () => {
},
});
});
it('should render button inside UploadList when listStyle is picture-card', () => {
const wrapper = mount(
<Upload
action="http://jsonplaceholder.typicode.com/posts/"
listType="picture-card"
fileList={[
{
uid: '0',
name: 'xxx.png',
},
]}
showUploadList
>
<button className="trigger" type="button">
upload
</button>
</Upload>,
);
expect(wrapper.exists('.ant-upload-list button.trigger')).toBe(true);
wrapper.setProps({ showUploadList: false });
expect(wrapper.exists('.ant-upload-list button.trigger')).toBe(false);
});
});

View File

@ -61,13 +61,13 @@ class Avatar extends React.Component {
};
render() {
const { loading, imageUrl } = this.state;
const uploadButton = (
<div>
{this.state.loading ? <LoadingOutlined /> : <PlusOutlined />}
<div className="ant-upload-text">Upload</div>
{loading ? <LoadingOutlined /> : <PlusOutlined />}
<div style={{ marginTop: 8 }}>Upload</div>
</div>
);
const { imageUrl } = this.state;
return (
<Upload
name="avatar"

View File

@ -37,9 +37,7 @@ const props = {
ReactDOM.render(
<Upload {...props}>
<Button>
<UploadOutlined /> Click to Upload
</Button>
<Button icon={<UploadOutlined />}>Click to Upload</Button>
</Upload>,
mountNode,
);

View File

@ -45,9 +45,7 @@ const props = {
ReactDOM.render(
<Upload {...props}>
<Button>
<UploadOutlined /> Click to Upload
</Button>
<Button icon={<UploadOutlined />}>Click to Upload</Button>
</Upload>,
mountNode,
);

View File

@ -50,9 +50,7 @@ const props = {
ReactDOM.render(
<Upload {...props}>
<Button>
<UploadOutlined /> Upload
</Button>
<Button icon={<UploadOutlined />}>Upload</Button>
</Upload>,
mountNode,
);

View File

@ -19,9 +19,7 @@ import { UploadOutlined } from '@ant-design/icons';
ReactDOM.render(
<Upload action="https://www.mocky.io/v2/5cc8019d300000980a055e76" directory>
<Button>
<UploadOutlined /> Upload Directory
</Button>
<Button icon={<UploadOutlined />}>Upload Directory</Button>
</Upload>,
mountNode,
);

View File

@ -116,11 +116,11 @@ class PicturesWall extends React.Component {
const uploadButton = (
<div>
<PlusOutlined />
<div className="ant-upload-text">Upload</div>
<div style={{ marginTop: 8 }}>Upload</div>
</div>
);
return (
<div className="clearfix">
<>
<Upload
action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
listType="picture-card"
@ -134,23 +134,10 @@ class PicturesWall extends React.Component {
<Modal visible={previewVisible} footer={null} onCancel={this.handleCancel}>
<img alt="example" style={{ width: '100%' }} src={previewImage} />
</Modal>
</div>
</>
);
}
}
ReactDOM.render(<PicturesWall />, mountNode);
```
```css
/* you can make up upload button and sample style by using stylesheets */
.ant-upload-select-picture-card i {
color: #999;
font-size: 32px;
}
.ant-upload-select-picture-card .ant-upload-text {
margin-top: 8px;
color: #666;
}
```

View File

@ -64,9 +64,7 @@ class MyUpload extends React.Component {
};
return (
<Upload {...props} fileList={this.state.fileList}>
<Button>
<UploadOutlined /> Upload
</Button>
<Button icon={<UploadOutlined />}>Upload</Button>
</Upload>
);
}

View File

@ -56,6 +56,13 @@ class PicturesWall extends React.Component {
status: 'done',
url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
},
{
uid: '-xxx',
percent: 50,
name: 'image.png',
status: 'uploading',
url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
},
{
uid: '-5',
name: 'image.png',
@ -85,11 +92,11 @@ class PicturesWall extends React.Component {
const uploadButton = (
<div>
<PlusOutlined />
<div className="ant-upload-text">Upload</div>
<div style={{ marginTop: 8 }}>Upload</div>
</div>
);
return (
<div className="clearfix">
<>
<Upload
action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
listType="picture-card"
@ -107,23 +114,10 @@ class PicturesWall extends React.Component {
>
<img alt="example" style={{ width: '100%' }} src={previewImage} />
</Modal>
</div>
</>
);
}
}
ReactDOM.render(<PicturesWall />, mountNode);
```
```css
/* you can make up upload button and sample style by using stylesheets */
.ant-upload-select-picture-card i {
color: #999;
font-size: 32px;
}
.ant-upload-select-picture-card .ant-upload-text {
margin-top: 8px;
color: #666;
}
```

View File

@ -32,32 +32,24 @@ const fileList = [
},
];
const props = {
action: 'https://www.mocky.io/v2/5cc8019d300000980a055e76',
listType: 'picture',
defaultFileList: [...fileList],
};
const props2 = {
action: 'https://www.mocky.io/v2/5cc8019d300000980a055e76',
listType: 'picture',
defaultFileList: [...fileList],
className: 'upload-list-inline',
};
ReactDOM.render(
<>
<Upload {...props}>
<Button>
<UploadOutlined /> Upload
</Button>
<Upload
action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
listType="picture"
defaultFileList={[...fileList]}
>
<Button icon={<UploadOutlined />}>Upload</Button>
</Upload>
<br />
<br />
<Upload {...props2}>
<Button>
<UploadOutlined /> Upload
</Button>
<Upload
action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
listType="picture"
defaultFileList={[...fileList]}
className="upload-list-inline"
>
<Button icon={<UploadOutlined />}>Upload</Button>
</Upload>
</>,
mountNode,
@ -75,10 +67,4 @@ ReactDOM.render(
.upload-list-inline [class*='-upload-list-rtl'] .ant-upload-list-item {
float: right;
}
.upload-list-inline .ant-upload-animate-enter {
animation-name: uploadAnimateInlineIn;
}
.upload-list-inline .ant-upload-animate-leave {
animation-name: uploadAnimateInlineOut;
}
```

View File

@ -34,9 +34,7 @@ const props = {
ReactDOM.render(
<Upload {...props}>
<Button>
<UploadOutlined /> Upload
</Button>
<Button icon={<UploadOutlined />}>Upload</Button>
</Upload>,
mountNode,
);

View File

@ -43,9 +43,7 @@ const props = {
ReactDOM.render(
<>
<Upload {...props}>
<Button>
<UploadOutlined /> Upload
</Button>
<Button icon={<UploadOutlined />}>Upload</Button>
</Upload>
</>,
mountNode,

View File

@ -56,9 +56,7 @@ const props = {
ReactDOM.render(
<Upload {...props}>
<Button>
<UploadOutlined /> Upload
</Button>
<Button icon={<UploadOutlined />}>Upload</Button>
</Upload>,
mountNode,
);

View File

@ -82,9 +82,7 @@ class Demo extends React.Component {
return (
<>
<Upload {...props}>
<Button>
<UploadOutlined /> Select File
</Button>
<Button icon={<UploadOutlined />}>Select File</Button>
</Upload>
<Button
type="primary"

View File

@ -36,9 +36,7 @@ const Uploader = () => {
};
return (
<Upload {...props}>
<Button>
<UploadOutlined /> Upload png only
</Button>
<Button icon={<UploadOutlined />}>Upload png only</Button>
</Upload>
);
};

View File

@ -114,9 +114,7 @@ class AliyunOSSUpload extends React.Component {
};
return (
<Upload {...props}>
<Button>
<UploadOutlined /> Click to Upload
</Button>
<Button icon={<UploadOutlined />}>Click to Upload</Button>
</Upload>
);
}

View File

@ -56,6 +56,8 @@ export interface ShowUploadListInterface {
showRemoveIcon?: boolean;
showPreviewIcon?: boolean;
showDownloadIcon?: boolean;
removeIcon?: React.ReactNode;
downloadIcon?: React.ReactNode;
}
export interface UploadLocale {
@ -133,4 +135,5 @@ export interface UploadListProps<T = any> {
previewFile?: PreviewFileHandler;
iconRender?: (file: UploadFile<T>, listType?: UploadListType) => React.ReactNode;
isImageUrl?: (file: UploadFile) => boolean;
appendAction?: React.ReactNode;
}

View File

@ -34,8 +34,6 @@
}
&&-select-picture-card {
display: table;
float: left;
width: @upload-picture-card-size;
height: @upload-picture-card-size;
margin-right: 8px;
@ -46,15 +44,14 @@
border: @border-width-base dashed @border-color-base;
border-radius: @border-radius-base;
cursor: pointer;
transition: border-color 0.3s ease;
transition: border-color 0.3s;
> .@{upload-prefix-cls} {
display: table-cell;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
height: 100%;
padding: @padding-xs;
text-align: center;
vertical-align: middle;
}
&:hover {
@ -408,17 +405,16 @@
}
&-container {
float: left;
display: inline-block;
width: @upload-picture-card-size;
height: @upload-picture-card-size;
margin: 0 @margin-xs @margin-xs 0;
vertical-align: top;
}
.@{upload-item} {
float: left;
width: @upload-picture-card-size;
height: @upload-picture-card-size;
margin: 0 @margin-xs @margin-xs 0;
height: 100%;
margin: 0;
}
.@{upload-item}-info {
@ -515,6 +511,7 @@
.@{upload-item}-progress {
bottom: 32px;
width: calc(100% - 14px);
padding-left: 0;
}
}

View File

@ -7,7 +7,7 @@ title: 结果页
结果页是用一个页面反馈操作结果,是反馈模式中最强的一种。
## 何时使用
## 何时使用
当完成一个流程操作后,需给与用户明确的结果反馈时,例如分步表单的最后一步。<br/> 当有大量的信息需要在结果页展示时。

View File

@ -90,7 +90,12 @@ module.exports = {
config.resolve.alias = { ...config.resolve.alias, react: require.resolve('react') };
} else if (process.env.ESBUILD) {
// use esbuild
config.optimization.minimizer = [new EsbuildPlugin(), new CssMinimizerPlugin()];
config.optimization.minimizer = [
new EsbuildPlugin({
target: 'chrome49',
}),
new CssMinimizerPlugin(),
];
}
alertBabelConfig(config.module.rules);

View File

@ -99,7 +99,9 @@ if (process.env.RUN_ENV === 'PRODUCTION') {
config.optimization.usedExports = true;
// use esbuild
if (process.env.ESBUILD || process.env.CSB_REPO) {
config.optimization.minimizer[0] = new EsbuildPlugin();
config.optimization.minimizer[0] = new EsbuildPlugin({
target: 'chrome49',
});
}
config.plugins.push(