merge featrue into master (#30636)

* feat: add Table expandable fixed (#29959)

* fix: Use flex gap of space (#30023)

* fix: use flex gap when supported

* test: update snapshot

* refactor: Use single hooks

* feat: Allow breadcrumb component in PageHeader (#30019)

* Allow breadcrumb component in PageHeader

* Allow breadcrumb component in PageHeader

* Allow breadcrumb component in PageHeader

* Rename variable

rename var from _breadcrumbRender to breadcrumbRenderDomFromProps

* feat: add onChange for Statistic.Countdown (#30265)

* feat: add onChange for countdown

* update the demo

* feat(upload): add onDrop (#30319)

* feat(upload): Add onDrop

* Replace "if prop" logic with existential operator

* Remove redundant conditional

* feat(upload): itemRender add actions params (#30236)

* feat(upload): itemRender add actions params

* chore: optimize type definition

* chore: update doc

* chore: rename actions

* chore: trigger ci

* chore: rename method name of actions

* feat: Add missing dutch translations (#30389)

* feat: Add missing dutch translations

* fix: Translate remaining english values

* fix: Update snapshot for ui tests

* test: increase code coverage to 100% (#30415)

* test: fix Space code coverage

* test: should use nl_BE locale for DatePicker

* fix: Switch tabIndex type (#30416)

* feat: updated Romanian internationalization (#30419)

* feat: updated Romanian internationalization

* fixed lint error

* feat: Menu support accessibility & keyboard access (#30382)

* chore: Use focus style

* fix: prefixCls

* fix: prefixCls

* fix: inline tooltip

* fix: inlineCollapse logic

* fix: ts definition

* test: Update snapshot

* test: Update snapshot

* fix: dropdown logic

* test: Update snapshot

* test: Fix some test  case

* bump rc-menu

* test: More test case

* fix test finder

* test: fix test case

* test: Update snapshot

* test: Update snapshot

* chore: Update ssr effect

* test: Update ConfigProvider snapshot

* test: Fix Table Filter test case

* test: Fix table test case

* chore: Update style

* chore: beauti css

* bump rc-menu

* test: update snapshot

* test: update snapshot

* test: Fix menu test

* test: Fix test case

* test: Coverage

* chore: clean up

* bump rc-menu

* ehance accessibility style

* feat(radioGroup): support data-* and aria-* props (#30507)

close #30501

* feat: Typography add italic type (#30458)

* Typography增加斜体字支持

* update snapshot

* 文档添加版本号

Co-authored-by: lidahao <lidahao@sisyphe.com.cn>

* chore: alpha Menu fix merge (#30546)

* chore: Update script

* bump alpha version

* chore: Update script desc

* chore: bump rc-tabs & rc-mentions

* chore: Adjust style

* chore: 4.16.0-alpha.1

* test: Fix mention test case

* fix: sider of layout width style

* chore: bump 4.16.0-alpha.2

* fix: Tabs tabBarGutter should work as expected (#30545)

close #30526

* feat: Table summary support sticky mode (#30631)

* chore: Bump rc-table

* feat: Patch summary fixed color

* fix: style className

* test: Update snapshot

Co-authored-by: xrkffgg <xrkffgg@gmail.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: 二货机器人 <smith3816@gmail.com>
Co-authored-by: gepd <guillermoepd@hotmail.com>
Co-authored-by: appleshell <appleshell@outlook.com>
Co-authored-by: Eric Bonow <ebonow@hotmail.com>
Co-authored-by: xrkffgg <xrkffgg@vip.qq.com>
Co-authored-by: Kermit <kermitlx@outlook.com>
Co-authored-by: Lewis <lewisfidlers@gmail.com>
Co-authored-by: afc163 <afc163@gmail.com>
Co-authored-by: Ștefan Filip <stefy.filip@gmail.com>
Co-authored-by: vldh <alwaysloseall@sina.com>
Co-authored-by: lidahao <lidahao@sisyphe.com.cn>
This commit is contained in:
陈帅 2021-05-24 16:24:00 +08:00 committed by GitHub
parent 9275866f24
commit 0d1b1c40a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
76 changed files with 2086 additions and 1043 deletions

View File

@ -1,5 +1,5 @@
import * as React from 'react';
import { detectFlexGapSupported } from '../../_util/styleChecker';
import { detectFlexGapSupported } from '../styleChecker';
export default () => {
const [flexible, setFlexible] = React.useState(false);

View File

@ -15038,20 +15038,22 @@ exports[`ConfigProvider components List prefixCls 1`] = `
exports[`ConfigProvider components Menu configProvider 1`] = `
<ul
class="config-menu config-menu-light config-menu-root config-menu-inline"
class="config-menu config-menu-root config-menu-inline config-menu-light"
data-menu-list="true"
role="menu"
tabindex="0"
>
<li
class="config-menu-submenu config-menu-submenu-inline config-menu-submenu-open"
role="menuitem"
role="none"
>
<div
aria-expanded="true"
aria-haspopup="true"
aria-owns="bamboo$Menu"
class="config-menu-submenu-title"
role="button"
role="menuitem"
style="padding-left:24px"
tabindex="-1"
title="bamboo"
>
bamboo
@ -15061,11 +15063,10 @@ exports[`ConfigProvider components Menu configProvider 1`] = `
</div>
<ul
class="config-menu config-menu-sub config-menu-inline"
id="bamboo$Menu"
role="menu"
data-menu-list="true"
>
<li
class=" config-menu-item-group"
class="config-menu-item-group"
>
<div
class="config-menu-item-group-title"
@ -15080,6 +15081,7 @@ exports[`ConfigProvider components Menu configProvider 1`] = `
class="config-menu-item config-menu-item-only-child"
role="menuitem"
style="padding-left:48px"
tabindex="-1"
>
Light
</li>
@ -15092,20 +15094,22 @@ exports[`ConfigProvider components Menu configProvider 1`] = `
exports[`ConfigProvider components Menu configProvider componentSize large 1`] = `
<ul
class="config-menu config-menu-light config-menu-root config-menu-inline"
class="config-menu config-menu-root config-menu-inline config-menu-light"
data-menu-list="true"
role="menu"
tabindex="0"
>
<li
class="config-menu-submenu config-menu-submenu-inline config-menu-submenu-open"
role="menuitem"
role="none"
>
<div
aria-expanded="true"
aria-haspopup="true"
aria-owns="bamboo$Menu"
class="config-menu-submenu-title"
role="button"
role="menuitem"
style="padding-left:24px"
tabindex="-1"
title="bamboo"
>
bamboo
@ -15115,11 +15119,10 @@ exports[`ConfigProvider components Menu configProvider componentSize large 1`] =
</div>
<ul
class="config-menu config-menu-sub config-menu-inline"
id="bamboo$Menu"
role="menu"
data-menu-list="true"
>
<li
class=" config-menu-item-group"
class="config-menu-item-group"
>
<div
class="config-menu-item-group-title"
@ -15134,6 +15137,7 @@ exports[`ConfigProvider components Menu configProvider componentSize large 1`] =
class="config-menu-item config-menu-item-only-child"
role="menuitem"
style="padding-left:48px"
tabindex="-1"
>
Light
</li>
@ -15146,20 +15150,22 @@ exports[`ConfigProvider components Menu configProvider componentSize large 1`] =
exports[`ConfigProvider components Menu configProvider componentSize middle 1`] = `
<ul
class="config-menu config-menu-light config-menu-root config-menu-inline"
class="config-menu config-menu-root config-menu-inline config-menu-light"
data-menu-list="true"
role="menu"
tabindex="0"
>
<li
class="config-menu-submenu config-menu-submenu-inline config-menu-submenu-open"
role="menuitem"
role="none"
>
<div
aria-expanded="true"
aria-haspopup="true"
aria-owns="bamboo$Menu"
class="config-menu-submenu-title"
role="button"
role="menuitem"
style="padding-left:24px"
tabindex="-1"
title="bamboo"
>
bamboo
@ -15169,11 +15175,10 @@ exports[`ConfigProvider components Menu configProvider componentSize middle 1`]
</div>
<ul
class="config-menu config-menu-sub config-menu-inline"
id="bamboo$Menu"
role="menu"
data-menu-list="true"
>
<li
class=" config-menu-item-group"
class="config-menu-item-group"
>
<div
class="config-menu-item-group-title"
@ -15188,6 +15193,7 @@ exports[`ConfigProvider components Menu configProvider componentSize middle 1`]
class="config-menu-item config-menu-item-only-child"
role="menuitem"
style="padding-left:48px"
tabindex="-1"
>
Light
</li>
@ -15200,20 +15206,22 @@ exports[`ConfigProvider components Menu configProvider componentSize middle 1`]
exports[`ConfigProvider components Menu configProvider virtual and dropdownMatchSelectWidth 1`] = `
<ul
class="ant-menu ant-menu-light ant-menu-root ant-menu-inline"
class="ant-menu ant-menu-root ant-menu-inline ant-menu-light"
data-menu-list="true"
role="menu"
tabindex="0"
>
<li
class="ant-menu-submenu ant-menu-submenu-inline ant-menu-submenu-open"
role="menuitem"
role="none"
>
<div
aria-expanded="true"
aria-haspopup="true"
aria-owns="bamboo$Menu"
class="ant-menu-submenu-title"
role="button"
role="menuitem"
style="padding-left:24px"
tabindex="-1"
title="bamboo"
>
bamboo
@ -15223,11 +15231,10 @@ exports[`ConfigProvider components Menu configProvider virtual and dropdownMatch
</div>
<ul
class="ant-menu ant-menu-sub ant-menu-inline"
id="bamboo$Menu"
role="menu"
data-menu-list="true"
>
<li
class=" ant-menu-item-group"
class="ant-menu-item-group"
>
<div
class="ant-menu-item-group-title"
@ -15242,6 +15249,7 @@ exports[`ConfigProvider components Menu configProvider virtual and dropdownMatch
class="ant-menu-item ant-menu-item-only-child"
role="menuitem"
style="padding-left:48px"
tabindex="-1"
>
Light
</li>
@ -15254,20 +15262,22 @@ exports[`ConfigProvider components Menu configProvider virtual and dropdownMatch
exports[`ConfigProvider components Menu normal 1`] = `
<ul
class="ant-menu ant-menu-light ant-menu-root ant-menu-inline"
class="ant-menu ant-menu-root ant-menu-inline ant-menu-light"
data-menu-list="true"
role="menu"
tabindex="0"
>
<li
class="ant-menu-submenu ant-menu-submenu-inline ant-menu-submenu-open"
role="menuitem"
role="none"
>
<div
aria-expanded="true"
aria-haspopup="true"
aria-owns="bamboo$Menu"
class="ant-menu-submenu-title"
role="button"
role="menuitem"
style="padding-left:24px"
tabindex="-1"
title="bamboo"
>
bamboo
@ -15277,11 +15287,10 @@ exports[`ConfigProvider components Menu normal 1`] = `
</div>
<ul
class="ant-menu ant-menu-sub ant-menu-inline"
id="bamboo$Menu"
role="menu"
data-menu-list="true"
>
<li
class=" ant-menu-item-group"
class="ant-menu-item-group"
>
<div
class="ant-menu-item-group-title"
@ -15296,6 +15305,7 @@ exports[`ConfigProvider components Menu normal 1`] = `
class="ant-menu-item ant-menu-item-only-child"
role="menuitem"
style="padding-left:48px"
tabindex="-1"
>
Light
</li>
@ -15308,20 +15318,22 @@ exports[`ConfigProvider components Menu normal 1`] = `
exports[`ConfigProvider components Menu prefixCls 1`] = `
<ul
class="prefix-Menu prefix-Menu-light prefix-Menu-root prefix-Menu-inline"
class="prefix-Menu prefix-Menu-root prefix-Menu-inline prefix-Menu-light"
data-menu-list="true"
role="menu"
tabindex="0"
>
<li
class="prefix-Menu-submenu prefix-Menu-submenu-inline prefix-Menu-submenu-open"
role="menuitem"
role="none"
>
<div
aria-expanded="true"
aria-haspopup="true"
aria-owns="bamboo$Menu"
class="prefix-Menu-submenu-title"
role="button"
role="menuitem"
style="padding-left:24px"
tabindex="-1"
title="bamboo"
>
bamboo
@ -15331,11 +15343,11 @@ exports[`ConfigProvider components Menu prefixCls 1`] = `
</div>
<ul
class="prefix-Menu prefix-Menu-sub prefix-Menu-inline"
id="bamboo$Menu"
role="menu"
data-menu-list="true"
>
<li
class=" prefix-Menu-item-group"
class="prefix-Menu-item-group"
prefixcls="prefix-Menu"
>
<div
class="prefix-Menu-item-group-title"
@ -15348,8 +15360,10 @@ exports[`ConfigProvider components Menu prefixCls 1`] = `
>
<li
class="prefix-Menu-item prefix-Menu-item-only-child"
prefixcls="prefix-Menu"
role="menuitem"
style="padding-left:48px"
tabindex="-1"
>
Light
</li>
@ -22853,12 +22867,15 @@ exports[`ConfigProvider components Table configProvider 1`] = `
class="config-table-filter-dropdown"
>
<ul
class="config-dropdown-menu config-dropdown-menu-light config-dropdown-menu-root config-dropdown-menu-vertical"
class="config-dropdown-menu config-dropdown-menu-root config-dropdown-menu-vertical config-dropdown-menu-light"
data-menu-list="true"
role="menu"
tabindex="0"
>
<li
class="config-dropdown-menu-item"
role="menuitem"
tabindex="-1"
>
<label
class="config-checkbox-wrapper"
@ -22881,13 +22898,14 @@ exports[`ConfigProvider components Table configProvider 1`] = `
</li>
<li
class="config-dropdown-menu-submenu config-dropdown-menu-submenu-vertical"
role="menuitem"
role="none"
>
<div
aria-expanded="false"
aria-haspopup="true"
class="config-dropdown-menu-submenu-title"
role="button"
role="menuitem"
tabindex="-1"
title="Submenu"
>
Submenu
@ -23124,12 +23142,15 @@ exports[`ConfigProvider components Table configProvider componentSize large 1`]
class="config-table-filter-dropdown"
>
<ul
class="config-dropdown-menu config-dropdown-menu-light config-dropdown-menu-root config-dropdown-menu-vertical"
class="config-dropdown-menu config-dropdown-menu-root config-dropdown-menu-vertical config-dropdown-menu-light"
data-menu-list="true"
role="menu"
tabindex="0"
>
<li
class="config-dropdown-menu-item"
role="menuitem"
tabindex="-1"
>
<label
class="config-checkbox-wrapper"
@ -23152,13 +23173,14 @@ exports[`ConfigProvider components Table configProvider componentSize large 1`]
</li>
<li
class="config-dropdown-menu-submenu config-dropdown-menu-submenu-vertical"
role="menuitem"
role="none"
>
<div
aria-expanded="false"
aria-haspopup="true"
class="config-dropdown-menu-submenu-title"
role="button"
role="menuitem"
tabindex="-1"
title="Submenu"
>
Submenu
@ -23395,12 +23417,15 @@ exports[`ConfigProvider components Table configProvider componentSize middle 1`]
class="config-table-filter-dropdown"
>
<ul
class="config-dropdown-menu config-dropdown-menu-light config-dropdown-menu-root config-dropdown-menu-vertical"
class="config-dropdown-menu config-dropdown-menu-root config-dropdown-menu-vertical config-dropdown-menu-light"
data-menu-list="true"
role="menu"
tabindex="0"
>
<li
class="config-dropdown-menu-item"
role="menuitem"
tabindex="-1"
>
<label
class="config-checkbox-wrapper"
@ -23423,13 +23448,14 @@ exports[`ConfigProvider components Table configProvider componentSize middle 1`]
</li>
<li
class="config-dropdown-menu-submenu config-dropdown-menu-submenu-vertical"
role="menuitem"
role="none"
>
<div
aria-expanded="false"
aria-haspopup="true"
class="config-dropdown-menu-submenu-title"
role="button"
role="menuitem"
tabindex="-1"
title="Submenu"
>
Submenu
@ -23666,12 +23692,15 @@ exports[`ConfigProvider components Table configProvider virtual and dropdownMatc
class="ant-table-filter-dropdown"
>
<ul
class="ant-dropdown-menu ant-dropdown-menu-light ant-dropdown-menu-root ant-dropdown-menu-vertical"
class="ant-dropdown-menu ant-dropdown-menu-root ant-dropdown-menu-vertical ant-dropdown-menu-light"
data-menu-list="true"
role="menu"
tabindex="0"
>
<li
class="ant-dropdown-menu-item"
role="menuitem"
tabindex="-1"
>
<label
class="ant-checkbox-wrapper"
@ -23694,13 +23723,14 @@ exports[`ConfigProvider components Table configProvider virtual and dropdownMatc
</li>
<li
class="ant-dropdown-menu-submenu ant-dropdown-menu-submenu-vertical"
role="menuitem"
role="none"
>
<div
aria-expanded="false"
aria-haspopup="true"
class="ant-dropdown-menu-submenu-title"
role="button"
role="menuitem"
tabindex="-1"
title="Submenu"
>
Submenu
@ -23937,12 +23967,15 @@ exports[`ConfigProvider components Table normal 1`] = `
class="ant-table-filter-dropdown"
>
<ul
class="ant-dropdown-menu ant-dropdown-menu-light ant-dropdown-menu-root ant-dropdown-menu-vertical"
class="ant-dropdown-menu ant-dropdown-menu-root ant-dropdown-menu-vertical ant-dropdown-menu-light"
data-menu-list="true"
role="menu"
tabindex="0"
>
<li
class="ant-dropdown-menu-item"
role="menuitem"
tabindex="-1"
>
<label
class="ant-checkbox-wrapper"
@ -23965,13 +23998,14 @@ exports[`ConfigProvider components Table normal 1`] = `
</li>
<li
class="ant-dropdown-menu-submenu ant-dropdown-menu-submenu-vertical"
role="menuitem"
role="none"
>
<div
aria-expanded="false"
aria-haspopup="true"
class="ant-dropdown-menu-submenu-title"
role="button"
role="menuitem"
tabindex="-1"
title="Submenu"
>
Submenu
@ -24208,12 +24242,15 @@ exports[`ConfigProvider components Table prefixCls 1`] = `
class="prefix-Table-filter-dropdown"
>
<ul
class="ant-dropdown-menu ant-dropdown-menu-light ant-dropdown-menu-root ant-dropdown-menu-vertical"
class="ant-dropdown-menu ant-dropdown-menu-root ant-dropdown-menu-vertical ant-dropdown-menu-light"
data-menu-list="true"
role="menu"
tabindex="0"
>
<li
class="ant-dropdown-menu-item"
role="menuitem"
tabindex="-1"
>
<label
class="ant-checkbox-wrapper"
@ -24236,13 +24273,14 @@ exports[`ConfigProvider components Table prefixCls 1`] = `
</li>
<li
class="ant-dropdown-menu-submenu ant-dropdown-menu-submenu-vertical"
role="menuitem"
role="none"
>
<div
aria-expanded="false"
aria-haspopup="true"
class="ant-dropdown-menu-submenu-title"
role="button"
role="menuitem"
tabindex="-1"
title="Submenu"
>
Submenu

View File

@ -77,10 +77,10 @@ The following APIs are shared by DatePicker, RangePicker.
### Common Methods
| Name | Description | Version |
| --- | --- | --- |
| blur() | Remove focus | |
| focus() | Get focus | |
| Name | Description | Version |
| ------- | ------------ | ------- |
| blur() | Remove focus | |
| focus() | Get focus | |
### DatePicker

View File

@ -5,8 +5,15 @@ import { PickerLocale } from '../generatePicker';
// Merge into a locale object
const locale: PickerLocale = {
lang: {
monthPlaceholder: 'Selecteer maand',
placeholder: 'Selecteer datum',
quarterPlaceholder: 'Selecteer kwartaal',
rangeMonthPlaceholder: ['Begin maand', 'Eind maand'],
rangePlaceholder: ['Begin datum', 'Eind datum'],
rangeWeekPlaceholder: ['Begin week', 'Eind week'],
rangeYearPlaceholder: ['Begin jaar', 'Eind jaar'],
weekPlaceholder: 'Selecteer week',
yearPlaceholder: 'Selecteer jaar',
...CalendarLocale,
},
timePickerLocale: {

View File

@ -5,8 +5,15 @@ import { PickerLocale } from '../generatePicker';
// Merge into a locale object
const locale: PickerLocale = {
lang: {
monthPlaceholder: 'Selecteer maand',
placeholder: 'Selecteer datum',
quarterPlaceholder: 'Selecteer kwartaal',
rangeMonthPlaceholder: ['Begin maand', 'Eind maand'],
rangePlaceholder: ['Begin datum', 'Eind datum'],
rangeWeekPlaceholder: ['Begin week', 'Eind week'],
rangeYearPlaceholder: ['Begin jaar', 'Eind jaar'],
weekPlaceholder: 'Selecteer week',
yearPlaceholder: 'Selecteer jaar',
...CalendarLocale,
},
timePickerLocale: {

View File

@ -71,7 +71,9 @@ describe('DropdownButton', () => {
<Menu.Item>foo</Menu.Item>
</Menu>
);
const wrapper = mount(<Dropdown.Button mouseEnterDelay={1} mouseLeaveDelay={2} overlay={menu} />);
const wrapper = mount(
<Dropdown.Button mouseEnterDelay={1} mouseLeaveDelay={2} overlay={menu} />,
);
expect(wrapper.find('Dropdown').props().mouseEnterDelay).toBe(1);
expect(wrapper.find('Dropdown').props().mouseLeaveDelay).toBe(2);
});

View File

@ -29,9 +29,11 @@ interface DropdownButtonInterface extends React.FC<DropdownButtonProps> {
}
const DropdownButton: DropdownButtonInterface = props => {
const { getPopupContainer: getContextPopupContainer, getPrefixCls, direction } = React.useContext(
ConfigContext,
);
const {
getPopupContainer: getContextPopupContainer,
getPrefixCls,
direction,
} = React.useContext(ConfigContext);
const {
prefixCls: customizePrefixCls,

View File

@ -101,8 +101,7 @@ const Dropdown: DropdownInterface = props => {
);
// menu cannot be selectable in dropdown defaultly
// menu should be focusable in dropdown defaultly
const { selectable = false, focusable = true, expandIcon } = overlayProps;
const { selectable = false, expandIcon } = overlayProps;
const overlayNodeExpandIcon =
typeof expandIcon !== 'undefined' && React.isValidElement(expandIcon) ? (
@ -119,7 +118,6 @@ const Dropdown: DropdownInterface = props => {
: cloneElement(overlayNode, {
mode: 'vertical',
selectable,
focusable,
expandIcon: overlayNodeExpandIcon,
});

View File

@ -43,7 +43,8 @@
}
&-hidden,
&-menu-hidden {
&-menu-hidden,
&-menu-submenu-hidden {
display: none;
}

View File

@ -8,7 +8,7 @@ import ResponsiveObserve, {
ScreenMap,
responsiveArray,
} from '../_util/responsiveObserve';
import useFlexGapSupport from './hooks/useFlexGapSupport';
import useFlexGapSupport from '../_util/hooks/useFlexGapSupport';
const RowAligns = tuple('top', 'middle', 'bottom', 'stretch');
const RowJustify = tuple('start', 'end', 'center', 'space-around', 'space-between');

View File

@ -21,7 +21,6 @@ const dimensionMaxMap = {
export interface SiderContextProps {
siderCollapsed?: boolean;
collapsedWidth?: number | string;
}
export const SiderContext: React.Context<SiderContextProps> = React.createContext({});
@ -219,7 +218,6 @@ const Sider = React.forwardRef<HTMLDivElement, SiderProps>(
<SiderContext.Provider
value={{
siderCollapsed: collapsed,
collapsedWidth,
}}
>
{renderSider()}

File diff suppressed because it is too large Load Diff

View File

@ -204,16 +204,16 @@ describe('Layout', () => {
</Sider>,
);
wrapper.find('.ant-menu-item').simulate('mouseenter');
wrapper.find('.ant-menu-item').hostNodes().simulate('mouseenter');
jest.runAllTimers();
wrapper.update();
expect(wrapper.find('.ant-tooltip-inner').length).toBeFalsy();
wrapper.find('.ant-menu-item').simulate('mouseout');
wrapper.find('.ant-menu-item').hostNodes().simulate('mouseout');
jest.runAllTimers();
wrapper.update();
wrapper.setProps({ collapsed: true });
wrapper.find('.ant-menu-item').simulate('mouseenter');
wrapper.find('.ant-menu-item').hostNodes().simulate('mouseenter');
jest.runAllTimers();
wrapper.update();
expect(wrapper.find('.ant-tooltip-inner').length).toBeTruthy();

View File

@ -2,6 +2,7 @@
@import '../../style/mixins/index';
@layout-prefix-cls: ~'@{ant-prefix}-layout';
@layout-menu-prefix-cls: ~'@{ant-prefix}-menu';
.@{layout-prefix-cls} {
display: flex;
@ -66,6 +67,10 @@
// https://github.com/ant-design/ant-design/issues/7967
// solution from https://stackoverflow.com/a/33132624/3040605
padding-top: 0.1px;
.@{layout-menu-prefix-cls}.@{layout-menu-prefix-cls}-inline-collapsed {
width: auto;
}
}
&-has-trigger {

View File

@ -211251,7 +211251,7 @@ exports[`Locale Provider should display the text as nl 1`] = `
type="button"
>
<span>
Annuleren
Annuleer
</span>
</button>
<button
@ -211329,7 +211329,7 @@ exports[`Locale Provider should display the text as nl 1`] = `
>
<input
class="ant-input ant-transfer-list-search"
placeholder="Zoeken"
placeholder="Zoek hier"
type="text"
value=""
/>
@ -211521,7 +211521,7 @@ exports[`Locale Provider should display the text as nl 1`] = `
>
<input
class="ant-input ant-transfer-list-search"
placeholder="Zoeken"
placeholder="Zoek hier"
type="text"
value=""
/>
@ -212763,7 +212763,7 @@ exports[`Locale Provider should display the text as nl 1`] = `
type="button"
>
<span>
Annuleren
Annuleer
</span>
</button>
<button

View File

@ -1,11 +1,7 @@
import React from 'react';
import { mount } from 'enzyme';
import ConfigProvider from '../../config-provider';
import { Modal } from '../..';
import zhCN from '../zh_CN';
class Demo extends React.Component {

View File

@ -1,21 +1,37 @@
/* eslint-disable no-template-curly-in-string */
import Pagination from 'rc-pagination/lib/locale/nl_BE';
import DatePicker from '../date-picker/locale/nl_BE';
import TimePicker from '../time-picker/locale/nl_BE';
import Calendar from '../calendar/locale/nl_BE';
import { Locale } from '../locale-provider';
const typeTemplate = '${label} is geen geldige ${type}';
const localeValues: Locale = {
locale: 'nl-be',
Pagination,
DatePicker,
TimePicker,
Calendar,
global: {
placeholder: 'Maak een selectie',
},
Table: {
filterTitle: 'FilterMenu',
cancelSort: 'Klik om sortering te annuleren',
collapse: 'Rij inklappen',
emptyText: 'Geen data',
expand: 'Rij uitklappen',
filterConfirm: 'OK',
filterEmptyText: 'Geen filters',
filterReset: 'Reset',
filterTitle: 'Filteren',
selectAll: 'Selecteer huidige pagina',
selectInvert: 'Selecteer huidige pagina',
selectInvert: 'Keer volgorde om',
selectNone: 'Maak selectie leeg',
selectionAll: 'Selecteer alle data',
sortTitle: 'Sorteren',
triggerAsc: 'Klik om oplopend te sorteren',
triggerDesc: 'Klik om aflopend te sorteren',
},
Modal: {
okText: 'OK',
@ -27,20 +43,92 @@ const localeValues: Locale = {
cancelText: 'Annuleer',
},
Transfer: {
searchPlaceholder: 'Zoek hier',
itemUnit: 'item',
itemsUnit: 'items',
remove: 'Verwijder',
removeAll: 'Verwijder alles',
removeCurrent: 'Verwijder huidige pagina',
searchPlaceholder: 'Zoek hier',
selectAll: 'Selecteer alles',
selectCurrent: 'Selecteer huidige pagina',
selectInvert: 'Huidige pagina omkeren',
titles: ['', ''],
},
Upload: {
downloadFile: 'Bestand downloaden',
previewFile: 'Preview file',
removeFile: 'Verwijder bestand',
uploadError: 'Fout tijdens uploaden',
uploading: 'Uploaden...',
removeFile: 'Bestand verwijderen',
uploadError: 'Upload fout',
previewFile: 'Preview bestand',
downloadFile: 'Download bestand',
},
Empty: {
description: 'Geen gegevens',
},
Icon: {
icon: 'icoon',
},
Text: {
edit: 'Bewerken',
copy: 'kopiëren',
copied: 'Gekopieerd',
expand: 'Uitklappen',
},
PageHeader: {
back: 'Terug',
},
Form: {
optional: '(optioneel)',
defaultValidateMessages: {
default: 'Validatiefout voor ${label}',
required: 'Gelieve ${label} in te vullen',
enum: '${label} moet één van [${enum}] zijn',
whitespace: '${label} mag geen blanco teken zijn',
date: {
format: '${label} heeft een ongeldig formaat',
parse: '${label} kan niet naar een datum omgezet worden',
invalid: '${label} is een ongeldige datum',
},
types: {
string: typeTemplate,
method: typeTemplate,
array: typeTemplate,
object: typeTemplate,
number: typeTemplate,
date: typeTemplate,
boolean: typeTemplate,
integer: typeTemplate,
float: typeTemplate,
regexp: typeTemplate,
email: typeTemplate,
url: typeTemplate,
hex: typeTemplate,
},
string: {
len: '${label} moet ${len} karakters lang zijn',
min: '${label} moet minimaal ${min} karakters lang zijn',
max: '${label} mag maximaal ${max} karakters lang zijn',
range: '${label} moet tussen ${min}-${max} karakters lang zijn',
},
number: {
len: '${label} moet gelijk zijn aan ${len}',
min: '${label} moet minimaal ${min} zijn',
max: '${label} mag maximaal ${max} zijn',
range: '${label} moet tussen ${min}-${max} liggen',
},
array: {
len: 'Moeten ${len} ${label} zijn',
min: 'Minimaal ${min} ${label}',
max: 'maximaal ${max} ${label}',
range: 'Het aantal ${label} moet tussen ${min}-${max} liggen',
},
pattern: {
mismatch: '${label} komt niet overeen met het patroon ${pattern}',
},
},
},
Image: {
preview: 'Voorbeeld',
},
};
export default localeValues;

View File

@ -1,9 +1,12 @@
/* eslint-disable no-template-curly-in-string */
import Pagination from 'rc-pagination/lib/locale/nl_NL';
import DatePicker from '../date-picker/locale/nl_NL';
import TimePicker from '../time-picker/locale/nl_NL';
import Calendar from '../calendar/locale/nl_NL';
import { Locale } from '../locale-provider';
const typeTemplate = '${label} is geen geldige ${type}';
const localeValues: Locale = {
locale: 'nl',
Pagination,
@ -14,36 +17,49 @@ const localeValues: Locale = {
placeholder: 'Maak een selectie',
},
Table: {
filterTitle: 'Filteren',
filterConfirm: 'OK',
filterReset: 'Reset',
selectAll: 'Selecteer huidige pagina',
selectInvert: 'Deselecteer huidige pagina',
sortTitle: 'Sorteren',
expand: 'Rij uitklappen',
cancelSort: 'Klik om sortering te annuleren',
collapse: 'Rij inklappen',
emptyText: 'Geen data',
expand: 'Rij uitklappen',
filterConfirm: 'OK',
filterEmptyText: 'Geen filters',
filterReset: 'Reset',
filterTitle: 'Filteren',
selectAll: 'Selecteer huidige pagina',
selectInvert: 'Keer volgorde om',
selectNone: 'Maak selectie leeg',
selectionAll: 'Selecteer alle data',
sortTitle: 'Sorteren',
triggerAsc: 'Klik om oplopend te sorteren',
triggerDesc: 'Klik om aflopend te sorteren',
},
Modal: {
okText: 'OK',
cancelText: 'Annuleren',
cancelText: 'Annuleer',
justOkText: 'OK',
},
Popconfirm: {
okText: 'OK',
cancelText: 'Annuleren',
cancelText: 'Annuleer',
},
Transfer: {
titles: ['', ''],
searchPlaceholder: 'Zoeken',
itemUnit: 'item',
itemsUnit: 'items',
remove: 'Verwijder',
removeAll: 'Verwijder alles',
removeCurrent: 'Verwijder huidige pagina',
searchPlaceholder: 'Zoek hier',
selectAll: 'Selecteer alles',
selectCurrent: 'Selecteer huidige pagina',
selectInvert: 'Huidige pagina omkeren',
titles: ['', ''],
},
Upload: {
uploading: 'Uploaden...',
downloadFile: 'Bestand downloaden',
previewFile: 'Preview file',
removeFile: 'Verwijder bestand',
uploadError: 'Fout tijdens uploaden',
previewFile: 'Bekijk bestand',
downloadFile: 'Downloaden bestand',
uploading: 'Uploaden...',
},
Empty: {
description: 'Geen gegevens',
@ -53,13 +69,66 @@ const localeValues: Locale = {
},
Text: {
edit: 'Bewerken',
copy: 'Kopieren',
copy: 'kopiëren',
copied: 'Gekopieerd',
expand: 'Uitklappen',
},
PageHeader: {
back: 'Terug',
},
Form: {
optional: '(optioneel)',
defaultValidateMessages: {
default: 'Validatiefout voor ${label}',
required: 'Gelieve ${label} in te vullen',
enum: '${label} moet één van [${enum}] zijn',
whitespace: '${label} mag geen blanco teken zijn',
date: {
format: '${label} heeft een ongeldig formaat',
parse: '${label} kan niet naar een datum omgezet worden',
invalid: '${label} is een ongeldige datum',
},
types: {
string: typeTemplate,
method: typeTemplate,
array: typeTemplate,
object: typeTemplate,
number: typeTemplate,
date: typeTemplate,
boolean: typeTemplate,
integer: typeTemplate,
float: typeTemplate,
regexp: typeTemplate,
email: typeTemplate,
url: typeTemplate,
hex: typeTemplate,
},
string: {
len: '${label} moet ${len} karakters lang zijn',
min: '${label} moet minimaal ${min} karakters lang zijn',
max: '${label} mag maximaal ${max} karakters lang zijn',
range: '${label} moet tussen ${min}-${max} karakters lang zijn',
},
number: {
len: '${label} moet gelijk zijn aan ${len}',
min: '${label} moet minimaal ${min} zijn',
max: '${label} mag maximaal ${max} zijn',
range: '${label} moet tussen ${min}-${max} liggen',
},
array: {
len: 'Moeten ${len} ${label} zijn',
min: 'Minimaal ${min} ${label}',
max: 'maximaal ${max} ${label}',
range: 'Het aantal ${label} moet tussen ${min}-${max} liggen',
},
pattern: {
mismatch: '${label} komt niet overeen met het patroon ${pattern}',
},
},
},
Image: {
preview: 'Voorbeeld',
},
};
export default localeValues;

View File

@ -1,9 +1,12 @@
/* eslint-disable no-template-curly-in-string */
import Pagination from 'rc-pagination/lib/locale/ro_RO';
import DatePicker from '../date-picker/locale/ro_RO';
import TimePicker from '../time-picker/locale/ro_RO';
import Calendar from '../calendar/locale/ro_RO';
import { Locale } from '../locale-provider';
const typeTemplate = '${label} nu conține tipul corect (${type})';
const localeValues: Locale = {
locale: 'ro',
Pagination,
@ -17,11 +20,18 @@ const localeValues: Locale = {
filterTitle: 'Filtrează',
filterConfirm: 'OK',
filterReset: 'Resetează',
filterEmptyText: 'Fără filtre',
emptyText: 'Nu există date',
selectAll: 'Selectează pagina curentă',
selectInvert: 'Inversează pagina curentă',
selectNone: 'Șterge selecția',
selectionAll: 'Selectează toate datele',
sortTitle: 'Ordonează',
expand: 'Extinde rândul',
collapse: 'Micșorează rândul',
triggerDesc: 'Apasă pentru ordonare descrescătoare',
triggerAsc: 'Apasă pentru ordonare crescătoare',
cancelSort: 'Apasă pentru a anula ordonarea',
},
Modal: {
okText: 'OK',
@ -37,6 +47,12 @@ const localeValues: Locale = {
searchPlaceholder: 'Căutare',
itemUnit: 'element',
itemsUnit: 'elemente',
remove: 'Șterge',
selectCurrent: 'Selectează pagina curentă',
removeCurrent: 'Șterge pagina curentă',
selectAll: 'Selectează toate datele',
removeAll: 'Șterge toate datele',
selectInvert: 'Inversează pagina curentă',
},
Upload: {
uploading: 'Se transferă...',
@ -60,6 +76,59 @@ const localeValues: Locale = {
PageHeader: {
back: 'înapoi',
},
Form: {
optional: '(opțional)',
defaultValidateMessages: {
default: 'Eroare la validarea câmpului ${label}',
required: 'Vă rugăm introduceți ${label}',
enum: '${label} trebuie să fie una din valorile [${enum}]',
whitespace: '${label} nu poate fi gol',
date: {
format: '${label} - data nu este în formatul corect',
parse: '${label} nu poate fi convertit la o dată',
invalid: '${label} este o dată invalidă',
},
types: {
string: typeTemplate,
method: typeTemplate,
array: typeTemplate,
object: typeTemplate,
number: typeTemplate,
date: typeTemplate,
boolean: typeTemplate,
integer: typeTemplate,
float: typeTemplate,
regexp: typeTemplate,
email: typeTemplate,
url: typeTemplate,
hex: typeTemplate,
},
string: {
len: '${label} trebuie să conțină ${len} caractere',
min: '${label} trebuie să conțină cel puțin ${min} caractere',
max: '${label} trebuie să conțină cel mult ${max} caractere',
range: '${label} trebuie să conțină între ${min}-${max} caractere',
},
number: {
len: '${label} trebuie să conțină ${len} cifre',
min: '${label} trebuie să fie minim ${min}',
max: '${label} trebuie să fie maxim ${max}',
range: '${label} trebuie să fie între ${min}-${max}',
},
array: {
len: '${label} trebuie să conțină ${len} elemente',
min: '${label} trebuie să conțină cel puțin ${min} elemente',
max: '${label} trebuie să conțină cel mult ${max} elemente',
range: '${label} trebuie să conțină între ${min}-${max} elemente',
},
pattern: {
mismatch: '${label} nu respectă șablonul ${pattern}',
},
},
},
Image: {
preview: 'Preview',
},
};
export default localeValues;

View File

@ -81,14 +81,14 @@ describe('Mentions', () => {
it('loading', () => {
const wrapper = mount(<Mentions loading />);
simulateInput(wrapper, '@');
expect(wrapper.find('.ant-mentions-dropdown-menu-item').length).toBe(1);
expect(wrapper.find('li.ant-mentions-dropdown-menu-item').length).toBe(1);
expect(wrapper.find('.ant-spin').length).toBeTruthy();
});
it('notFoundContent', () => {
const wrapper = mount(<Mentions notFoundContent={<span className="bamboo-light" />} />);
simulateInput(wrapper, '@');
expect(wrapper.find('.ant-mentions-dropdown-menu-item').length).toBe(1);
expect(wrapper.find('li.ant-mentions-dropdown-menu-item').length).toBe(1);
expect(wrapper.find('.bamboo-light').length).toBeTruthy();
});
});

View File

@ -4,12 +4,16 @@ import { DirectionType } from '../config-provider';
export type MenuTheme = 'light' | 'dark';
export interface MenuContextProps {
prefixCls: string;
inlineCollapsed: boolean;
antdMenuTheme?: MenuTheme;
direction?: DirectionType;
firstLevel: boolean;
}
const MenuContext = createContext<MenuContextProps>({
prefixCls: '',
firstLevel: true,
inlineCollapsed: false,
});

View File

@ -14,77 +14,72 @@ export interface MenuItemProps extends Omit<RcMenuItemProps, 'title'> {
}
export default class MenuItem extends React.Component<MenuItemProps> {
static isMenuItem = true;
static contextType = MenuContext;
context: MenuContextProps;
renderItemChildren(inlineCollapsed: boolean) {
const { icon, children, level, rootPrefixCls } = this.props;
const { prefixCls, firstLevel } = this.context;
const { icon, children } = this.props;
// inline-collapsed.md demo 依赖 span 来隐藏文字,有 icon 属性,则内部包裹一个 span
// ref: https://github.com/ant-design/ant-design/pull/23456
if (!icon || (isValidElement(children) && children.type === 'span')) {
if (children && inlineCollapsed && level === 1 && typeof children === 'string') {
return (
<div className={`${rootPrefixCls}-inline-collapsed-noicon`}>{children.charAt(0)}</div>
);
if (children && inlineCollapsed && firstLevel && typeof children === 'string') {
return <div className={`${prefixCls}-inline-collapsed-noicon`}>{children.charAt(0)}</div>;
}
return children;
}
return <span>{children}</span>;
return <span className={`${prefixCls}-title-content`}>{children}</span>;
}
renderItem = ({ siderCollapsed }: SiderContextProps) => {
const { level, className, children, rootPrefixCls } = this.props;
const { prefixCls, firstLevel, inlineCollapsed, direction } = this.context;
const { className, children } = this.props;
const { title, icon, danger, ...rest } = this.props;
return (
<MenuContext.Consumer>
{({ inlineCollapsed, direction }: MenuContextProps) => {
let tooltipTitle = title;
if (typeof title === 'undefined') {
tooltipTitle = level === 1 ? children : '';
} else if (title === false) {
tooltipTitle = '';
}
const tooltipProps: TooltipProps = {
title: tooltipTitle,
};
let tooltipTitle = title;
if (typeof title === 'undefined') {
tooltipTitle = firstLevel ? children : '';
} else if (title === false) {
tooltipTitle = '';
}
const tooltipProps: TooltipProps = {
title: tooltipTitle,
};
if (!siderCollapsed && !inlineCollapsed) {
tooltipProps.title = null;
// Reset `visible` to fix control mode tooltip display not correct
// ref: https://github.com/ant-design/ant-design/issues/16742
tooltipProps.visible = false;
}
const childrenLength = toArray(children).length;
return (
<Tooltip
{...tooltipProps}
placement={direction === 'rtl' ? 'left' : 'right'}
overlayClassName={`${rootPrefixCls}-inline-collapsed-tooltip`}
>
<Item
{...rest}
className={classNames(
{
[`${rootPrefixCls}-item-danger`]: danger,
[`${rootPrefixCls}-item-only-child`]:
(icon ? childrenLength + 1 : childrenLength) === 1,
},
className,
)}
title={title}
>
{cloneElement(icon, {
className: classNames(
isValidElement(icon) ? icon.props?.className : '',
`${rootPrefixCls}-item-icon`,
),
})}
{this.renderItemChildren(inlineCollapsed)}
</Item>
</Tooltip>
);
}}
</MenuContext.Consumer>
if (!siderCollapsed && !inlineCollapsed) {
tooltipProps.title = null;
// Reset `visible` to fix control mode tooltip display not correct
// ref: https://github.com/ant-design/ant-design/issues/16742
tooltipProps.visible = false;
}
const childrenLength = toArray(children).length;
return (
<Tooltip
{...tooltipProps}
placement={direction === 'rtl' ? 'left' : 'right'}
overlayClassName={`${prefixCls}-inline-collapsed-tooltip`}
>
<Item
{...rest}
className={classNames(
{
[`${prefixCls}-item-danger`]: danger,
[`${prefixCls}-item-only-child`]: (icon ? childrenLength + 1 : childrenLength) === 1,
},
className,
)}
title={typeof title === 'string' ? title : undefined}
>
{cloneElement(icon, {
className: classNames(
isValidElement(icon) ? icon.props?.className : '',
`${prefixCls}-item-icon`,
),
})}
{this.renderItemChildren(inlineCollapsed)}
</Item>
</Tooltip>
);
};

View File

@ -7,11 +7,10 @@ import { isValidElement } from '../_util/reactNode';
interface TitleEventEntity {
key: string;
domEvent: Event;
domEvent: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>;
}
export interface SubMenuProps {
rootPrefixCls?: string;
className?: string;
disabled?: boolean;
level?: number;
@ -28,14 +27,15 @@ export interface SubMenuProps {
class SubMenu extends React.Component<SubMenuProps, any> {
static contextType = MenuContext;
// fix issue:https://github.com/ant-design/ant-design/issues/8666
static isSubMenu = 1;
context: MenuContextProps;
renderTitle(inlineCollapsed: boolean) {
const { icon, title, level, rootPrefixCls } = this.props;
const { icon, title, level } = this.props;
const { prefixCls } = this.context;
if (!icon) {
return inlineCollapsed && level === 1 && title && typeof title === 'string' ? (
<div className={`${rootPrefixCls}-inline-collapsed-noicon`}>{title.charAt(0)}</div>
<div className={`${prefixCls}-inline-collapsed-noicon`}>{title.charAt(0)}</div>
) : (
title
);
@ -46,27 +46,27 @@ class SubMenu extends React.Component<SubMenuProps, any> {
return (
<>
{icon}
{titleIsSpan ? title : <span>{title}</span>}
{titleIsSpan ? title : <span className={`${prefixCls}-title-content`}>{title}</span>}
</>
);
}
render() {
const { rootPrefixCls, popupClassName } = this.props;
const { popupClassName } = this.props;
const { prefixCls, inlineCollapsed, antdMenuTheme } = this.context;
return (
<MenuContext.Consumer>
{({ inlineCollapsed, antdMenuTheme }: MenuContextProps) => (
<RcSubMenu
{...omit(this.props, ['icon'])}
title={this.renderTitle(inlineCollapsed)}
popupClassName={classNames(
rootPrefixCls,
`${rootPrefixCls}-${antdMenuTheme}`,
popupClassName,
)}
/>
)}
</MenuContext.Consumer>
<MenuContext.Provider
value={{
...this.context,
firstLevel: false,
}}
>
<RcSubMenu
{...omit(this.props, ['icon'])}
title={this.renderTitle(inlineCollapsed)}
popupClassName={classNames(prefixCls, `${prefixCls}-${antdMenuTheme}`, popupClassName)}
/>
</MenuContext.Provider>
);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -2,12 +2,16 @@
exports[`Menu Menu.Item with icon children auto wrap span 1`] = `
<ul
class="ant-menu ant-menu-light ant-menu-root ant-menu-vertical"
class="ant-menu ant-menu-root ant-menu-vertical ant-menu-light"
data-menu-list="true"
role="menu"
tabindex="0"
>
<li
class="ant-menu-item"
data-menu-id="rc-menu-uuid-test-1"
role="menuitem"
tabindex="-1"
>
<span
aria-label="mail"
@ -28,13 +32,17 @@ exports[`Menu Menu.Item with icon children auto wrap span 1`] = `
/>
</svg>
</span>
<span>
<span
class="ant-menu-title-content"
>
Navigation One
</span>
</li>
<li
class="ant-menu-item"
data-menu-id="rc-menu-uuid-test-2"
role="menuitem"
tabindex="-1"
>
<span
aria-label="mail"
@ -61,13 +69,16 @@ exports[`Menu Menu.Item with icon children auto wrap span 1`] = `
</li>
<li
class="ant-menu-submenu ant-menu-submenu-vertical"
role="menuitem"
role="none"
>
<div
aria-controls="rc-menu-uuid-test-3-popup"
aria-expanded="false"
aria-haspopup="true"
class="ant-menu-submenu-title"
role="button"
data-menu-id="rc-menu-uuid-test-3"
role="menuitem"
tabindex="-1"
>
<span
aria-label="mail"
@ -88,7 +99,9 @@ exports[`Menu Menu.Item with icon children auto wrap span 1`] = `
/>
</svg>
</span>
<span>
<span
class="ant-menu-title-content"
>
Navigation One
</span>
<i
@ -98,13 +111,16 @@ exports[`Menu Menu.Item with icon children auto wrap span 1`] = `
</li>
<li
class="ant-menu-submenu ant-menu-submenu-vertical"
role="menuitem"
role="none"
>
<div
aria-controls="rc-menu-uuid-test-4-popup"
aria-expanded="false"
aria-haspopup="true"
class="ant-menu-submenu-title"
role="button"
data-menu-id="rc-menu-uuid-test-4"
role="menuitem"
tabindex="-1"
>
<span
aria-label="mail"
@ -138,16 +154,19 @@ exports[`Menu Menu.Item with icon children auto wrap span 1`] = `
exports[`Menu rtl render component should be rendered correctly in RTL direction 1`] = `
<ul
class="ant-menu ant-menu-light ant-menu-root ant-menu-rtl ant-menu-vertical"
direction="rtl"
class="ant-menu ant-menu-root ant-menu-vertical ant-menu-light ant-menu-rtl"
data-menu-list="true"
dir="rtl"
role="menu"
tabindex="0"
>
<li
class="ant-menu-item"
role="menuitem"
tabindex="-1"
/>
<li
class=" ant-menu-item-group"
class="ant-menu-item-group"
>
<div
class="ant-menu-item-group-title"
@ -158,14 +177,14 @@ exports[`Menu rtl render component should be rendered correctly in RTL direction
</li>
<li
class="ant-menu-submenu ant-menu-submenu-vertical"
role="menuitem"
role="none"
>
<div
aria-expanded="false"
aria-haspopup="true"
class="ant-menu-submenu-title"
role="button"
title=""
role="menuitem"
tabindex="-1"
>
<i
class="ant-menu-submenu-arrow"
@ -177,13 +196,17 @@ exports[`Menu rtl render component should be rendered correctly in RTL direction
exports[`Menu should controlled collapse work 1`] = `
<ul
class="ant-menu ant-menu-light ant-menu-root ant-menu-inline"
class="ant-menu ant-menu-root ant-menu-inline ant-menu-light"
data-menu-list="true"
role="menu"
tabindex="0"
>
<li
class="ant-menu-item"
data-menu-id="rc-menu-uuid-test-1"
role="menuitem"
style="padding-left: 24px;"
tabindex="-1"
>
<span
aria-label="pie-chart"
@ -204,7 +227,9 @@ exports[`Menu should controlled collapse work 1`] = `
/>
</svg>
</span>
<span>
<span
class="ant-menu-title-content"
>
Option 1
</span>
</li>
@ -213,13 +238,17 @@ exports[`Menu should controlled collapse work 1`] = `
exports[`Menu should controlled collapse work 2`] = `
<ul
class="ant-menu ant-menu-light ant-menu-inline-collapsed ant-menu-root ant-menu-inline"
class="ant-menu ant-menu-root ant-menu-vertical ant-menu-light ant-menu-inline-collapsed"
data-menu-list="true"
role="menu"
tabindex="0"
>
<li
class="ant-menu-item"
data-menu-id="rc-menu-uuid-test-1"
role="menuitem"
style="padding-left: 24px;"
tabindex="-1"
>
<span
aria-label="pie-chart"
@ -240,7 +269,9 @@ exports[`Menu should controlled collapse work 2`] = `
/>
</svg>
</span>
<span>
<span
class="ant-menu-title-content"
>
Option 1
</span>
</li>

View File

@ -14,7 +14,6 @@ import Tooltip from '../../tooltip';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import collapseMotion from '../../_util/motion';
import { sleep } from '../../../tests/utils';
const { SubMenu } = Menu;
@ -22,35 +21,45 @@ const noop = () => {};
const expectSubMenuBehavior = (menu, enter = noop, leave = noop) => {
if (!menu.prop('openKeys') && !menu.prop('defaultOpenKeys')) {
expect(menu.find('.ant-menu-sub').length).toBe(0);
expect(menu.find('ul.ant-menu-sub').length).toBe(0);
}
menu.update();
expect(menu.find('.ant-menu-sub').length).toBe(0);
expect(menu.find('ul.ant-menu-sub').length).toBe(0);
const AnimationClassNames = {
horizontal: 'ant-slide-up-leave',
inline: 'ant-motion-collapse-leave',
vertical: 'ant-zoom-big-leave',
};
const mode = menu.prop('mode') || 'horizontal';
enter();
menu.update();
act(() => {
enter();
jest.runAllTimers();
menu.update();
});
function getSubMenu() {
if (mode === 'inline') {
return menu.find('.ant-menu-sub.ant-menu-inline').hostNodes().at(0);
return menu.find('ul.ant-menu-sub.ant-menu-inline').hostNodes().at(0);
}
return menu.find('.ant-menu-submenu-popup').hostNodes().at(0);
return menu.find('div.ant-menu-submenu-popup').hostNodes().at(0);
}
expect(
getSubMenu().hasClass('ant-menu-hidden') || getSubMenu().hasClass(AnimationClassNames[mode]),
).toBe(false);
leave();
menu.update();
).toBeFalsy();
expect(
getSubMenu().hasClass('ant-menu-hidden') || getSubMenu().hasClass(AnimationClassNames[mode]),
).toBe(true);
act(() => {
leave();
jest.runAllTimers();
menu.update();
});
if (getSubMenu().length) {
expect(
getSubMenu().hasClass('ant-menu-hidden') || getSubMenu().hasClass(AnimationClassNames[mode]),
).toBeTruthy();
}
};
describe('Menu', () => {
@ -93,11 +102,16 @@ describe('Menu', () => {
</Menu>
));
let div;
beforeEach(() => {
div = document.createElement('div');
document.body.appendChild(div);
jest.useFakeTimers();
});
afterEach(() => {
document.body.removeChild(div);
jest.useRealTimers();
});
@ -115,7 +129,7 @@ describe('Menu', () => {
<Menu.Item key="2">menu2</Menu.Item>
</Menu>,
);
expect(wrapper.find('.ant-menu-submenu-selected').length).toBe(1);
expect(wrapper.find('li.ant-menu-submenu-selected').length).toBe(1);
});
it('forceSubMenuRender', () => {
@ -145,7 +159,7 @@ describe('Menu', () => {
<Menu.Item key="2">menu2</Menu.Item>
</Menu>,
);
expect(wrapper.find('.ant-menu-sub').at(0).hasClass('ant-menu-hidden')).not.toBe(true);
expect(wrapper.exists('.ant-menu-sub')).toBeFalsy();
});
it('should accept defaultOpenKeys in mode inline', () => {
@ -171,7 +185,7 @@ describe('Menu', () => {
<Menu.Item key="2">menu2</Menu.Item>
</Menu>,
);
expect(wrapper.find('.ant-menu-sub').at(0).hasClass('ant-menu-hidden')).not.toBe(true);
expect(wrapper.find('PopupTrigger').first().prop('visible')).toBeTruthy();
});
it('should accept openKeys in mode horizontal', () => {
@ -184,7 +198,7 @@ describe('Menu', () => {
<Menu.Item key="2">menu2</Menu.Item>
</Menu>,
);
expect(wrapper.find('.ant-menu-sub').at(0).hasClass('ant-menu-hidden')).not.toBe(true);
expect(wrapper.find('PopupTrigger').first().prop('visible')).toBeTruthy();
});
it('should accept openKeys in mode inline', () => {
@ -197,7 +211,7 @@ describe('Menu', () => {
<Menu.Item key="2">menu2</Menu.Item>
</Menu>,
);
expect(wrapper.find('.ant-menu-sub').at(0).hasClass('ant-menu-hidden')).not.toBe(true);
expect(wrapper.find('InlineSubMenuList').first().prop('open')).toBeTruthy();
});
it('should accept openKeys in mode vertical', () => {
@ -210,7 +224,7 @@ describe('Menu', () => {
<Menu.Item key="2">menu2</Menu.Item>
</Menu>,
);
expect(wrapper.find('.ant-menu-sub').at(0).hasClass('ant-menu-hidden')).not.toBe(true);
expect(wrapper.find('PopupTrigger').first().prop('visible')).toBeTruthy();
});
it('test submenu in mode horizontal', () => {
@ -309,29 +323,28 @@ describe('Menu', () => {
</SubMenu>
</Menu>,
);
expect(wrapper.find('ul.ant-menu-sub').at(0).hasClass('ant-menu-inline')).toBe(true);
expect(wrapper.find('ul.ant-menu-sub').at(0).hasClass('ant-menu-hidden')).toBe(false);
expect(wrapper.find('InlineSubMenuList').prop('open')).toBeTruthy();
// inlineCollapsed
wrapper.setProps({ inlineCollapsed: true });
// 动画结束后套样式;
jest.runAllTimers();
wrapper.update();
wrapper.simulate('transitionEnd', { propertyName: 'width' });
act(() => {
jest.runAllTimers();
wrapper.update();
});
expect(wrapper.find('ul.ant-menu-root').at(0).hasClass('ant-menu-vertical')).toBe(true);
expect(wrapper.find('ul.ant-menu-sub:not(.ant-menu-hidden)').length).toBe(0);
expect(wrapper.find('ul.ant-menu-root').hasClass('ant-menu-vertical')).toBeTruthy();
expect(wrapper.find('PopupTrigger').prop('visible')).toBeFalsy();
// !inlineCollapsed
wrapper.setProps({ inlineCollapsed: false });
jest.runAllTimers();
wrapper.update();
act(() => {
jest.runAllTimers();
wrapper.update();
});
expect(wrapper.find('ul.ant-menu-sub').at(0).hasClass('ant-menu-inline')).toBe(true);
expect(wrapper.find('ul.ant-menu-sub').at(0).hasClass('ant-menu-hidden')).toBe(false);
expect(wrapper.find('ul.ant-menu-sub').last().hasClass('ant-menu-inline')).toBeTruthy();
expect(wrapper.find('InlineSubMenuList').prop('open')).toBeTruthy();
});
it('inlineCollapsed should works well when specify a not existed default openKeys', () => {
@ -435,11 +448,16 @@ describe('Menu', () => {
});
it('inline menu collapseMotion should be triggered', async () => {
jest.useRealTimers();
const onAppearEnd = jest.spyOn(collapseMotion, 'onAppearEnd');
collapseMotion.motionDeadline = 1;
const cloneMotion = {
...collapseMotion,
motionDeadline: 1,
};
const onOpenChange = jest.fn();
const onEnterEnd = jest.spyOn(cloneMotion, 'onEnterEnd');
const wrapper = mount(
<Menu mode="inline">
<Menu mode="inline" motion={cloneMotion} onOpenChange={onOpenChange}>
<SubMenu key="1" title="submenu1">
<Menu.Item key="submenu1">Option 1</Menu.Item>
<Menu.Item key="submenu2">Option 2</Menu.Item>
@ -447,10 +465,16 @@ describe('Menu', () => {
<Menu.Item key="2">menu2</Menu.Item>
</Menu>,
);
wrapper.find('.ant-menu-submenu-title').simulate('click');
await sleep(3000);
expect(onAppearEnd).toHaveBeenCalledTimes(1);
onAppearEnd.mockRestore();
wrapper.find('div.ant-menu-submenu-title').simulate('click');
act(() => {
jest.runAllTimers();
wrapper.update();
});
expect(onOpenChange).toHaveBeenCalled();
expect(onEnterEnd).toHaveBeenCalledTimes(1);
});
it('vertical with hover(default)', () => {
@ -539,7 +563,7 @@ describe('Menu', () => {
</Menu>,
);
wrapper.find('.ant-menu-item').simulate('mouseenter');
wrapper.find('.ant-menu-item').hostNodes().simulate('mouseenter');
act(() => {
jest.runAllTimers();
});
@ -601,7 +625,7 @@ describe('Menu', () => {
<Menu.Item key="test2">Navigation Two</Menu.Item>
</Menu>,
);
wrapper.find('Menu').at(1).simulate('mouseenter');
wrapper.find('ul.ant-menu-root').simulate('mouseenter');
expect(onMouseEnter).toHaveBeenCalled();
});
@ -620,10 +644,16 @@ describe('Menu', () => {
</a>
</Menu.Item>
</Menu>,
{ attachTo: div },
);
wrapper.find('MenuItem').first().simulate('mouseenter');
jest.runAllTimers();
wrapper.update();
wrapper.find('li.ant-menu-item').first().simulate('mouseenter');
act(() => {
jest.runAllTimers();
wrapper.update();
});
expect(wrapper.find('.ant-tooltip-inner').length).toBe(0);
});
@ -664,7 +694,7 @@ describe('Menu', () => {
</Menu>,
);
wrapper.find('.ant-menu-item').simulate('mouseenter');
wrapper.find('.ant-menu-item').hostNodes().simulate('mouseenter');
jest.runAllTimers();
wrapper.update();
@ -715,20 +745,25 @@ describe('Menu', () => {
</Menu.SubMenu>
</Menu>,
);
expect(wrapper.find('.ant-menu-item-selected').getDOMNode().textContent).toBe('Option 1');
wrapper.find('.ant-menu-item').at(1).simulate('click');
expect(wrapper.find('.ant-menu-item-selected').getDOMNode().textContent).toBe('Option 2');
expect(wrapper.find('li.ant-menu-item-selected').getDOMNode().textContent).toBe('Option 1');
wrapper.find('li.ant-menu-item').at(1).simulate('click');
expect(wrapper.find('li.ant-menu-item-selected').getDOMNode().textContent).toBe('Option 2');
wrapper.setProps({ inlineCollapsed: true });
jest.runAllTimers();
wrapper.update();
act(() => {
jest.runAllTimers();
wrapper.update();
});
expect(
wrapper
.find('Trigger')
.find('PopupTrigger')
.map(node => node.prop('popupVisible'))
.findIndex(node => !!node),
).toBe(-1);
wrapper.setProps({ inlineCollapsed: false });
expect(wrapper.find('.ant-menu-item-selected').getDOMNode().textContent).toBe('Option 2');
expect(wrapper.find('li.ant-menu-item-selected').getDOMNode().textContent).toBe('Option 2');
jest.useRealTimers();
});
@ -776,4 +811,14 @@ describe('Menu', () => {
wrapper.find('button').simulate('click');
expect(onOpenChange).toHaveBeenCalledWith([]);
});
it('Use first char as Icon when collapsed', () => {
const wrapper = mount(
<Menu mode="inline" inlineCollapsed>
<Menu.Item>Bamboo</Menu.Item>
</Menu>,
);
expect(wrapper.find('.ant-menu-inline-collapsed-noicon').text()).toEqual('B');
});
});

View File

@ -1,6 +1,7 @@
import * as React from 'react';
import RcMenu, { Divider, ItemGroup, MenuProps as RcMenuProps } from 'rc-menu';
import classNames from 'classnames';
import omit from 'rc-util/lib/omit';
import SubMenu, { SubMenuProps } from './SubMenu';
import Item, { MenuItemProps } from './MenuItem';
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
@ -17,16 +18,16 @@ export type MenuMode = 'vertical' | 'vertical-left' | 'vertical-right' | 'horizo
export interface MenuProps extends RcMenuProps {
theme?: MenuTheme;
inlineIndent?: number;
focusable?: boolean;
}
type InternalMenuProps = MenuProps & SiderContextProps;
type InternalMenuProps = MenuProps &
SiderContextProps & {
collapsedWidth?: string | number;
};
class InternalMenu extends React.Component<InternalMenuProps> {
static defaultProps: Partial<MenuProps> = {
className: '',
theme: 'light', // or dark
focusable: false,
};
constructor(props: InternalMenuProps) {
@ -56,7 +57,17 @@ class InternalMenu extends React.Component<InternalMenuProps> {
renderMenu = ({ getPopupContainer, getPrefixCls, direction }: ConfigConsumerProps) => {
const rootPrefixCls = getPrefixCls();
const { prefixCls: customizePrefixCls, className, theme, expandIcon } = this.props;
const {
prefixCls: customizePrefixCls,
className,
theme,
expandIcon,
...restProps
} = this.props;
const passedProps = omit(restProps, ['siderCollapsed', 'collapsedWidth']);
const inlineCollapsed = this.getInlineCollapsed();
const defaultMotions = {
horizontal: { motionName: `${rootPrefixCls}-slide-up` },
inline: collapseMotion,
@ -64,25 +75,22 @@ class InternalMenu extends React.Component<InternalMenuProps> {
};
const prefixCls = getPrefixCls('menu', customizePrefixCls);
const menuClassName = classNames(
`${prefixCls}-${theme}`,
{
[`${prefixCls}-inline-collapsed`]: this.getInlineCollapsed(),
},
className,
);
const menuClassName = classNames(`${prefixCls}-${theme}`, className);
return (
<MenuContext.Provider
value={{
inlineCollapsed: this.getInlineCollapsed() || false,
prefixCls,
inlineCollapsed: inlineCollapsed || false,
antdMenuTheme: theme,
direction,
firstLevel: true,
}}
>
<RcMenu
getPopupContainer={getPopupContainer}
{...this.props}
{...passedProps}
inlineCollapsed={inlineCollapsed}
className={menuClassName}
prefixCls={prefixCls}
direction={direction}

View File

@ -3,6 +3,13 @@
@import './status';
@menu-prefix-cls: ~'@{ant-prefix}-menu';
@menu-animation-duration-normal: 0.15s;
.accessibility-focus() {
box-shadow: 0 0 0 2px fade(@primary-color, 20%);
}
// TODO: Should remove icon style compatible in v5
// default theme
.@{menu-prefix-cls} {
@ -18,9 +25,14 @@
background: @menu-bg;
outline: none;
box-shadow: @box-shadow-base;
transition: background 0.3s, width 0.3s cubic-bezier(0.2, 0, 0, 1) 0s;
transition: background @animation-duration-slow,
width @animation-duration-slow cubic-bezier(0.2, 0, 0, 1) 0s;
.clearfix();
&&-root:focus-visible {
.accessibility-focus();
}
ul,
ol {
margin: 0;
@ -28,7 +40,8 @@
list-style: none;
}
&-hidden {
&-hidden,
&-submenu-hidden {
display: none;
}
@ -38,16 +51,18 @@
color: @menu-item-group-title-color;
font-size: @menu-item-group-title-font-size;
line-height: @menu-item-group-height;
transition: all 0.3s;
transition: all @animation-duration-slow;
}
&-horizontal &-submenu {
transition: border-color 0.3s @ease-in-out, background 0.3s @ease-in-out;
transition: border-color @animation-duration-slow @ease-in-out,
background @animation-duration-slow @ease-in-out;
}
&-submenu,
&-submenu-inline {
transition: border-color 0.3s @ease-in-out, background 0.3s @ease-in-out,
padding 0.15s @ease-in-out;
transition: border-color @animation-duration-slow @ease-in-out,
background @animation-duration-slow @ease-in-out,
padding @menu-animation-duration-normal @ease-in-out;
}
&-submenu-selected {
@ -61,7 +76,8 @@
&-submenu &-sub {
cursor: initial;
transition: background 0.3s @ease-in-out, padding 0.3s @ease-in-out;
transition: background @animation-duration-slow @ease-in-out,
padding @animation-duration-slow @ease-in-out;
}
&-item a {
@ -172,7 +188,7 @@
&-horizontal &-item,
&-horizontal &-submenu-title {
transition: border-color 0.3s, background 0.3s;
transition: border-color @animation-duration-slow, background @animation-duration-slow;
}
&-item,
@ -183,17 +199,22 @@
padding: @menu-item-padding;
white-space: nowrap;
cursor: pointer;
transition: border-color 0.3s, background 0.3s, padding 0.15s @ease-in-out;
transition: border-color @animation-duration-slow, background @animation-duration-slow,
padding @animation-duration-slow @ease-in-out;
.@{menu-prefix-cls}-item-icon,
.@{iconfont-css-prefix} {
min-width: 14px;
margin-right: @menu-icon-margin-right;
font-size: @menu-icon-size;
transition: font-size 0.15s @ease-out, margin 0.3s @ease-in-out, color 0.3s;
transition: font-size @menu-animation-duration-normal @ease-out,
margin @animation-duration-slow @ease-in-out, color @animation-duration-slow;
+ span {
margin-left: @menu-icon-margin-right;
opacity: 1;
transition: opacity 0.3s @ease-in-out, width 0.3s @ease-in-out, color 0.3s;
// transition: opacity @animation-duration-slow @ease-in-out,
// width @animation-duration-slow @ease-in-out, color @animation-duration-slow;
transition: opacity @animation-duration-slow @ease-in-out, margin @animation-duration-slow,
color @animation-duration-slow;
}
}
@ -203,6 +224,10 @@
margin-right: 0;
}
}
&:focus-visible {
.accessibility-focus();
}
}
& > &-item-divider {
@ -248,7 +273,7 @@
background-color: @menu-bg;
border-radius: @border-radius-base;
&-submenu-title::after {
transition: transform 0.3s @ease-in-out;
transition: transform @animation-duration-slow @ease-in-out;
}
}
@ -264,10 +289,11 @@
width: 10px;
color: @menu-item-color;
transform: translateY(-50%);
transition: transform 0.3s @ease-in-out;
transition: transform @animation-duration-slow @ease-in-out;
}
&-arrow {
// →
&::before,
&::after {
position: absolute;
@ -275,8 +301,9 @@
height: 1.5px;
background-color: currentColor;
border-radius: 2px;
transition: background 0.3s @ease-in-out, transform 0.3s @ease-in-out, top 0.3s @ease-in-out,
color 0.3s @ease-in-out;
transition: background @animation-duration-slow @ease-in-out,
transform @animation-duration-slow @ease-in-out, top @animation-duration-slow @ease-in-out,
color @animation-duration-slow @ease-in-out;
content: '';
}
&::before {
@ -292,7 +319,9 @@
color: @menu-highlight-color;
}
.@{menu-prefix-cls}-inline-collapsed &-arrow,
&-inline &-arrow {
// ↓
&::before {
transform: rotate(-45deg) translateX(2.5px);
}
@ -306,6 +335,7 @@
}
&-open&-inline > &-title > &-arrow {
// ↑
transform: translateY(-2px);
&::after {
transform: rotate(-45deg) translateX(-2.5px);
@ -398,7 +428,8 @@
border-right: @menu-item-active-border-width solid @menu-highlight-color;
transform: scaleY(0.0001);
opacity: 0;
transition: transform 0.15s @ease-out, opacity 0.15s @ease-out;
transition: transform @menu-animation-duration-normal @ease-out,
opacity @menu-animation-duration-normal @ease-out;
content: '';
}
}
@ -444,7 +475,8 @@
&::after {
transform: scaleY(1);
opacity: 1;
transition: transform 0.15s @ease-in-out, opacity 0.15s @ease-in-out;
transition: transform @menu-animation-duration-normal @ease-in-out,
opacity @menu-animation-duration-normal @ease-in-out;
}
}
@ -457,12 +489,33 @@
.@{menu-prefix-cls}-submenu-title {
padding-right: 34px;
}
// Motion enhance for first level
&.@{menu-prefix-cls}-root {
.@{menu-prefix-cls}-item,
.@{menu-prefix-cls}-submenu-title {
display: flex;
align-items: center;
transition: border-color @animation-duration-slow, background @animation-duration-slow,
padding 0.1s @ease-out;
> .@{menu-prefix-cls}-title-content {
flex: auto;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
}
> * {
flex: none;
}
}
}
}
&-inline-collapsed {
&:not(.@{ant-prefix}-layout-sider-children > ul) {
width: @menu-collapsed-width;
}
&&-inline-collapsed {
width: @menu-collapsed-width;
> .@{menu-prefix-cls}-item,
> .@{menu-prefix-cls}-item-group
> .@{menu-prefix-cls}-item-group-list
@ -475,8 +528,9 @@
left: 0;
padding: 0 ~'calc(50% - @{menu-icon-size-lg} / 2)';
text-overflow: clip;
.@{menu-prefix-cls}-submenu-arrow {
display: none;
opacity: 0;
}
.@{menu-prefix-cls}-item-icon,
@ -486,7 +540,6 @@
line-height: @menu-item-height;
+ span {
display: inline-block;
max-width: 0;
opacity: 0;
}
}

View File

@ -1,6 +1,7 @@
import React from 'react';
import { mount, render } from 'enzyme';
import PageHeader from '..';
import Breadcrumb from '../../breadcrumb';
import ConfigProvider from '../../config-provider';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
@ -50,6 +51,20 @@ describe('PageHeader', () => {
expect(wrapper.find('.ant-page-header-back')).toHaveLength(0);
});
it('pageHeader should have breadcrumb (component)', () => {
const routes = [
{
path: 'index',
breadcrumbName: 'First-level Menu',
},
];
const wrapper = mount(
<PageHeader title="Page Title" breadcrumb={<Breadcrumb routes={routes} />} />,
);
expect(wrapper.find('.ant-breadcrumb')).toHaveLength(1);
expect(wrapper.find('.ant-page-header-back')).toHaveLength(0);
});
it('pageHeader support breadcrumbRender', () => {
const wrapper = mount(
<PageHeader title="Page Title" breadcrumbRender={() => <div id="test">test</div>} />,

View File

@ -16,7 +16,7 @@ export interface PageHeaderProps {
title?: React.ReactNode;
subTitle?: React.ReactNode;
style?: React.CSSProperties;
breadcrumb?: BreadcrumbProps;
breadcrumb?: BreadcrumbProps | React.ReactElement<typeof Breadcrumb>;
breadcrumbRender?: (props: PageHeaderProps, defaultDom: React.ReactNode) => React.ReactNode;
tags?: React.ReactElement<TagType> | React.ReactElement<TagType>[];
footer?: React.ReactNode;
@ -156,10 +156,13 @@ const PageHeader: React.FC<PageHeaderProps> = props => {
const defaultBreadcrumbDom = getDefaultBreadcrumbDom();
const isBreadcrumbComponent = breadcrumb && 'props' in breadcrumb;
// support breadcrumbRender function
const breadcrumbDom =
const breadcrumbRenderDomFromProps =
breadcrumbRender?.(props, defaultBreadcrumbDom) || defaultBreadcrumbDom;
const breadcrumbDom = isBreadcrumbComponent ? breadcrumb : breadcrumbRenderDomFromProps;
const className = classNames(prefixCls, customizeClassName, {
'has-breadcrumb': !!breadcrumbDom,
'has-footer': !!footer,

View File

@ -192,4 +192,15 @@ describe('Radio Group', () => {
});
});
});
it('should support data-* or aria-* props', () => {
const wrapper = mount(
createRadioGroup({
'data-radio-group-id': 'radio-group-id',
'aria-label': 'radio-group',
}),
);
expect(wrapper.getDOMNode().getAttribute('data-radio-group-id')).toBe('radio-group-id');
expect(wrapper.getDOMNode().getAttribute('aria-label')).toBe('radio-group');
});
});

View File

@ -6,6 +6,7 @@ import { RadioGroupProps, RadioChangeEvent, RadioGroupButtonStyle } from './inte
import { ConfigContext } from '../config-provider';
import SizeContext from '../config-provider/SizeContext';
import { RadioGroupContextProvider } from './context';
import getDataOrAriaProps from '../_util/getDataOrAriaProps';
const RadioGroup = React.forwardRef<HTMLDivElement, RadioGroupProps>((props, ref) => {
const { getPrefixCls, direction } = React.useContext(ConfigContext);
@ -91,6 +92,7 @@ const RadioGroup = React.forwardRef<HTMLDivElement, RadioGroupProps>((props, ref
);
return (
<div
{...getDataOrAriaProps(props)}
className={classString}
style={style}
onMouseEnter={onMouseEnter}

View File

@ -20,19 +20,23 @@ export default function Item({
split,
wrap,
}: ItemProps) {
const { horizontalSize, verticalSize, latestIndex } = React.useContext(SpaceContext);
const { horizontalSize, verticalSize, latestIndex, supportFlexGap } = React.useContext(
SpaceContext,
);
let style: React.CSSProperties = {};
if (direction === 'vertical') {
if (index < latestIndex) {
style = { marginBottom: horizontalSize / (split ? 2 : 1) };
if (!supportFlexGap) {
if (direction === 'vertical') {
if (index < latestIndex) {
style = { marginBottom: horizontalSize / (split ? 2 : 1) };
}
} else {
style = {
...(index < latestIndex && { [marginDirection]: horizontalSize / (split ? 2 : 1) }),
...(wrap && { paddingBottom: verticalSize }),
};
}
} else {
style = {
...(index < latestIndex && { [marginDirection]: horizontalSize / (split ? 2 : 1) }),
...(wrap && { paddingBottom: verticalSize }),
};
}
if (children === null || children === undefined) {

View File

@ -408,6 +408,61 @@ exports[`renders ./components/space/demo/debug.md correctly 1`] = `
</div>
`;
exports[`renders ./components/space/demo/gap-in-line.md correctly 1`] = `
Array [
<button
aria-checked="false"
class="ant-switch"
role="switch"
type="button"
>
<div
class="ant-switch-handle"
/>
<span
class="ant-switch-inner"
/>
</button>,
<div
class="ant-space ant-space-horizontal ant-space-align-center"
style="flex-wrap:wrap;margin-bottom:-8px;width:310px;background:blue"
>
<div
class="ant-space-item"
style="margin-right:8px;padding-bottom:8px"
>
<div
style="width:150px;height:100px;background:red"
/>
</div>
<div
class="ant-space-item"
style="margin-right:8px;padding-bottom:8px"
>
<div
style="width:150px;height:100px;background:red"
/>
</div>
<div
class="ant-space-item"
style="margin-right:8px;padding-bottom:8px"
>
<div
style="width:150px;height:100px;background:red"
/>
</div>
<div
class="ant-space-item"
style="padding-bottom:8px"
>
<div
style="width:150px;height:100px;background:red"
/>
</div>
</div>,
]
`;
exports[`renders ./components/space/demo/size.md correctly 1`] = `
Array [
<div

View File

@ -0,0 +1,24 @@
import React from 'react';
import { mount } from 'enzyme';
import Space from '..';
// eslint-disable-next-line no-unused-vars
import * as styleChecker from '../../_util/styleChecker';
jest.mock('../../_util/styleChecker', () => ({
canUseDocElement: () => true,
isStyleSupport: () => true,
detectFlexGapSupported: () => true,
}));
describe('flex gap', () => {
it('should render width empty children', () => {
const wrapper = mount(
<Space>
<span />
<span />
</Space>,
);
expect(wrapper.getDOMNode().style['column-gap']).toBe('8px');
expect(wrapper.getDOMNode().style['row-gap']).toBe('8px');
});
});

View File

@ -0,0 +1,48 @@
---
order: 99
title:
zh-CN: Flex gap 样式
en-US: Flex gap style
debug: true
---
## zh-CN
Debug usage
## en-US
Debug usage
```tsx
import { Space, Switch } from 'antd';
const style: React.CSSProperties = {
width: 150,
height: 100,
background: 'red',
};
const Demo = () => {
const [singleCol, setSingleCol] = React.useState(false);
return (
<>
<Switch
checked={singleCol}
onChange={() => {
setSingleCol(!singleCol);
}}
/>
<Space style={{ width: singleCol ? 307 : 310, background: 'blue' }} size={[8, 8]} wrap>
<div style={style} />
<div style={style} />
<div style={style} />
<div style={style} />
</Space>
</>
);
};
ReactDOM.render(<Demo />, mountNode);
```

View File

@ -4,11 +4,13 @@ import toArray from 'rc-util/lib/Children/toArray';
import { ConfigContext } from '../config-provider';
import { SizeType } from '../config-provider/SizeContext';
import Item from './Item';
import useFlexGapSupport from '../_util/hooks/useFlexGapSupport';
export const SpaceContext = React.createContext({
latestIndex: 0,
horizontalSize: 0,
verticalSize: 0,
supportFlexGap: false,
});
export type SpaceSize = SizeType | number;
@ -51,6 +53,8 @@ const Space: React.FC<SpaceProps> = props => {
...otherProps
} = props;
const supportFlexGap = useFlexGapSupport();
const [horizontalSize, verticalSize] = React.useMemo(
() =>
((Array.isArray(size) ? size : [size, size]) as [SpaceSize, SpaceSize]).map(item =>
@ -61,10 +65,6 @@ const Space: React.FC<SpaceProps> = props => {
const childNodes = toArray(children, { keepEmpty: true });
if (childNodes.length === 0) {
return null;
}
const mergedAlign = align === undefined && direction === 'horizontal' ? 'center' : align;
const prefixCls = getPrefixCls('space', customizePrefixCls);
const cn = classNames(
@ -105,18 +105,33 @@ const Space: React.FC<SpaceProps> = props => {
/* eslint-enable */
});
const spaceContext = React.useMemo(
() => ({ horizontalSize, verticalSize, latestIndex, supportFlexGap }),
[horizontalSize, verticalSize, latestIndex, supportFlexGap],
);
// =========================== Render ===========================
if (childNodes.length === 0) {
return null;
}
const gapStyle: React.CSSProperties = {};
if (supportFlexGap) {
gapStyle.columnGap = horizontalSize;
gapStyle.rowGap = verticalSize;
}
return (
<div
className={cn}
style={{
...gapStyle,
...(wrap && { flexWrap: 'wrap', marginBottom: -verticalSize }),
...style,
}}
{...otherProps}
>
<SpaceContext.Provider value={{ horizontalSize, verticalSize, latestIndex }}>
{nodes}
</SpaceContext.Provider>
<SpaceContext.Provider value={spaceContext}>{nodes}</SpaceContext.Provider>
</div>
);
};

View File

@ -9,6 +9,7 @@ interface CountdownProps extends StatisticProps {
value?: countdownValueType;
format?: string;
onFinish?: () => void;
onChange?: (value?: countdownValueType) => void;
}
function getTime(value?: countdownValueType) {
@ -48,8 +49,15 @@ class Countdown extends React.Component<CountdownProps, {}> {
startTimer = () => {
if (this.countdownId) return;
const { onChange, value } = this.props;
const timestamp = getTime(value);
this.countdownId = window.setInterval(() => {
this.forceUpdate();
if (onChange && timestamp > Date.now()) {
onChange(timestamp - Date.now());
}
}, REFRESH_INTERVAL);
};

View File

@ -325,6 +325,29 @@ exports[`renders ./components/statistic/demo/countdown.md correctly 1`] = `
</div>
</div>
</div>
<div
class="ant-col ant-col-12"
style="padding-left:8px;padding-right:8px"
>
<div
class="ant-statistic"
>
<div
class="ant-statistic-title"
>
Countdown
</div>
<div
class="ant-statistic-content"
>
<span
class="ant-statistic-content-value"
>
00:00:10
</span>
</div>
</div>
</div>
</div>
`;

View File

@ -116,6 +116,21 @@ describe('Statistic', () => {
expect(onMouseLeave).toHaveBeenCalled();
});
describe('time onchange', () => {
it("called if time has't passed", async () => {
const deadline = Date.now() + 10 * 1000;
let remainingTime;
const onChange = value => {
remainingTime = value;
};
const wrapper = mount(<Statistic.Countdown value={deadline} onChange={onChange} />);
wrapper.update();
await sleep(100)
expect(remainingTime).toBeGreaterThan(0)
});
});
describe('time finished', () => {
it('not call if time already passed', () => {
const now = Date.now() - 1000;

View File

@ -23,6 +23,12 @@ function onFinish() {
console.log('finished!');
}
function onChange(val) {
if (4.95 * 1000 < val && val < 5 * 1000) {
console.log('changed!');
}
}
ReactDOM.render(
<Row gutter={16}>
<Col span={12}>
@ -34,6 +40,9 @@ ReactDOM.render(
<Col span={24} style={{ marginTop: 32 }}>
<Countdown title="Day Level" value={deadline} format="D 天 H 时 m 分 s 秒" />
</Col>
<Col span={12}>
<Countdown title="Countdown" value={Date.now() + 10 * 1000} onChange={onChange} />
</Col>
</Row>,
mountNode,
);

View File

@ -40,3 +40,4 @@ Display statistic number.
| value | Set target countdown time | number \| moment | - | |
| valueStyle | Set value css style | CSSProperties | - | |
| onFinish | Trigger when time's up | () => void | - | |
| onChange | Trigger when time's changing | (value: number) => void | - | 4.16.0 |

View File

@ -41,3 +41,4 @@ cover: https://gw.alipayobjects.com/zos/antfincdn/rcBNhLBrKbE/Statistic.svg
| value | 数值内容 | number \| moment | - | |
| valueStyle | 设置数值的样式 | CSSProperties | - | |
| onFinish | 倒计时完成时触发 | () => void | - | |
| onChange | 倒计时时间变化时触发 | (value: number) => void | - | 4.16.0 |

View File

@ -27,6 +27,7 @@ export interface SwitchProps {
autoFocus?: boolean;
style?: React.CSSProperties;
title?: string;
tabIndex?: number;
}
interface CompoundedComponent

View File

@ -1,6 +1,7 @@
/* eslint-disable react/no-multi-comp */
import React from 'react';
import { mount } from 'enzyme';
import { act } from 'react-dom/test-utils';
import Table from '..';
import Input from '../../input';
import Tooltip from '../../tooltip';
@ -11,11 +12,12 @@ import ConfigProvider from '../../config-provider';
// https://github.com/Semantic-Org/Semantic-UI-React/blob/72c45080e4f20b531fda2e3e430e384083d6766b/test/specs/modules/Dropdown/Dropdown-test.js#L73
const nativeEvent = { nativeEvent: { stopImmediatePropagation: () => {} } };
function getDropdownWrapper(wrapper) {
return mount(wrapper.find('Trigger').first().instance().getComponent());
}
describe('Table.filter', () => {
window.requestAnimationFrame = function (callback) {
return window.setTimeout(callback, 16);
};
window.cancelAnimationFrame = window.clearTimeout;
const filterFn = (value, record) => record.name.indexOf(value) !== -1;
const column = {
title: 'Name',
@ -96,6 +98,8 @@ describe('Table.filter', () => {
});
it('renders empty menu correctly', () => {
jest.useFakeTimers();
jest.spyOn(console, 'error').mockImplementation(() => undefined);
const wrapper = mount(
createTable({
@ -108,11 +112,18 @@ describe('Table.filter', () => {
}),
);
wrapper.find('span.ant-dropdown-trigger').simulate('click', nativeEvent);
expect(wrapper.find('Empty').length).toBe(1);
act(() => {
jest.runAllTimers();
wrapper.update();
});
expect(wrapper.find('Empty').length).toBeTruthy();
// eslint-disable-next-line no-console
expect(console.error).not.toHaveBeenCalled();
// eslint-disable-next-line no-console
console.error.mockRestore();
jest.useRealTimers();
});
it('renders radio filter correctly', () => {
@ -548,24 +559,44 @@ describe('Table.filter', () => {
// Open
wrapper.find('.ant-table-filter-trigger').simulate('click');
let dropdownWrapper = getDropdownWrapper(wrapper);
function getFilterMenu() {
return wrapper.find('FilterDropdown');
}
// Seems raf not trigger when in useEffect for async update
// Need trigger multiple times
function refreshTimer() {
for (let i = 0; i < 3; i += 1) {
act(() => {
jest.runAllTimers();
wrapper.update();
});
}
}
// Open Level2
getFilterMenu().find('div.ant-dropdown-menu-submenu-title').at(0).simulate('mouseEnter');
refreshTimer();
// Open Level3
getFilterMenu().find('div.ant-dropdown-menu-submenu-title').at(1).simulate('mouseEnter');
refreshTimer();
// Select Level3 value
getFilterMenu().find('li.ant-dropdown-menu-item').last().simulate('click');
getFilterMenu().find('.ant-table-filter-dropdown-btns .ant-btn-primary').simulate('click');
refreshTimer();
// select
dropdownWrapper.find('.ant-dropdown-menu-submenu-title').at(0).simulate('mouseEnter');
jest.runAllTimers();
dropdownWrapper = getDropdownWrapper(wrapper);
dropdownWrapper.find('.ant-dropdown-menu-submenu-title').at(1).simulate('mouseEnter');
jest.runAllTimers();
dropdownWrapper = getDropdownWrapper(wrapper);
dropdownWrapper.find('MenuItem').last().simulate('click');
dropdownWrapper.find('.ant-table-filter-dropdown-btns .ant-btn-primary').simulate('click');
onChange.mock.calls.forEach(([, currentFilters]) => {
const [, val] = Object.entries(currentFilters)[0];
expect(val).toEqual(['Jack']);
});
wrapper.update();
expect(renderedNames(wrapper)).toEqual(['Jack']);
dropdownWrapper.find('MenuItem').last().simulate('click');
// What's this? Is that a coverage case?
getFilterMenu().find('li.ant-dropdown-menu-item').last().simulate('click');
jest.useRealTimers();
});

View File

@ -1,4 +1,5 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import { mount, render } from 'enzyme';
import Table from '..';
import Checkbox from '../../checkbox';
@ -6,6 +7,9 @@ import { resetWarned } from '../../_util/devWarning';
import ConfigProvider from '../../config-provider';
describe('Table.rowSelection', () => {
window.requestAnimationFrame = callback => window.setTimeout(callback, 16);
window.cancelAnimationFrame = window.clearTimeout;
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
afterEach(() => {
@ -335,6 +339,8 @@ describe('Table.rowSelection', () => {
});
it('fires selectInvert event', () => {
jest.useFakeTimers();
const order = [];
const handleSelectInvert = jest.fn().mockImplementation(() => {
order.push('onSelectInvert');
@ -353,14 +359,24 @@ describe('Table.rowSelection', () => {
checkboxes.at(1).simulate('change', { target: { checked: true } });
// Open
wrapper.find('Trigger').setState({ popupVisible: true });
wrapper.find('span.ant-dropdown-trigger').simulate('mouseEnter');
const dropdownWrapper = mount(wrapper.find('Trigger').first().instance().getComponent());
dropdownWrapper.find('.ant-dropdown-menu-item').at(1).simulate('click');
// enzyme has bug for state sync.
// Let fresh multiple times to force sync back.
for (let i = 0; i < 3; i += 1) {
act(() => {
jest.runAllTimers();
wrapper.update();
});
}
wrapper.find('li.ant-dropdown-menu-item').at(1).simulate('click');
expect(handleSelectInvert).toHaveBeenCalledWith([1, 2, 3]);
expect(order).toEqual(['onChange', 'onSelectInvert', 'onChange']);
jest.useRealTimers();
});
it('fires selectNone event', () => {
@ -416,12 +432,12 @@ describe('Table.rowSelection', () => {
wrapper.find('Trigger').setState({ popupVisible: true });
const dropdownWrapper = mount(wrapper.find('Trigger').first().instance().getComponent());
expect(dropdownWrapper.find('.ant-dropdown-menu-item').length).toBe(4);
expect(dropdownWrapper.find('li.ant-dropdown-menu-item').length).toBe(4);
dropdownWrapper.find('.ant-dropdown-menu-item').at(2).simulate('click');
dropdownWrapper.find('li.ant-dropdown-menu-item').at(2).simulate('click');
expect(handleSelectOdd).toHaveBeenCalledWith([0, 1, 2, 3]);
dropdownWrapper.find('.ant-dropdown-menu-item').at(3).simulate('click');
dropdownWrapper.find('li.ant-dropdown-menu-item').at(3).simulate('click');
expect(handleSelectEven).toHaveBeenCalledWith([0, 1, 2, 3]);
});
@ -456,12 +472,12 @@ describe('Table.rowSelection', () => {
wrapper.find('Trigger').setState({ popupVisible: true });
const dropdownWrapper = mount(wrapper.find('Trigger').first().instance().getComponent());
expect(dropdownWrapper.find('.ant-dropdown-menu-item').length).toBe(2);
expect(dropdownWrapper.find('li.ant-dropdown-menu-item').length).toBe(2);
dropdownWrapper.find('.ant-dropdown-menu-item').at(0).simulate('click');
dropdownWrapper.find('li.ant-dropdown-menu-item').at(0).simulate('click');
expect(handleSelectOdd).toHaveBeenCalledWith([0, 1, 2, 3]);
dropdownWrapper.find('.ant-dropdown-menu-item').at(1).simulate('click');
dropdownWrapper.find('li.ant-dropdown-menu-item').at(1).simulate('click');
expect(handleSelectEven).toHaveBeenCalledWith([0, 1, 2, 3]);
});

View File

@ -601,12 +601,16 @@ exports[`Table.filter should support getPopupContainer 1`] = `
class="ant-table-filter-dropdown"
>
<ul
class="ant-dropdown-menu ant-dropdown-menu-light ant-dropdown-menu-root ant-dropdown-menu-vertical"
class="ant-dropdown-menu ant-dropdown-menu-root ant-dropdown-menu-vertical ant-dropdown-menu-light"
data-menu-list="true"
role="menu"
tabindex="0"
>
<li
class="ant-dropdown-menu-item"
data-menu-id="rc-menu-uuid-test-boy"
role="menuitem"
tabindex="-1"
>
<label
class="ant-checkbox-wrapper"
@ -630,7 +634,9 @@ exports[`Table.filter should support getPopupContainer 1`] = `
</li>
<li
class="ant-dropdown-menu-item"
data-menu-id="rc-menu-uuid-test-girl"
role="menuitem"
tabindex="-1"
>
<label
class="ant-checkbox-wrapper"
@ -654,13 +660,16 @@ exports[`Table.filter should support getPopupContainer 1`] = `
</li>
<li
class="ant-dropdown-menu-submenu ant-dropdown-menu-submenu-vertical"
role="menuitem"
role="none"
>
<div
aria-controls="rc-menu-uuid-test-title-popup"
aria-expanded="false"
aria-haspopup="true"
class="ant-dropdown-menu-submenu-title"
role="button"
data-menu-id="rc-menu-uuid-test-title"
role="menuitem"
tabindex="-1"
title="Title"
>
Title
@ -833,12 +842,16 @@ exports[`Table.filter should support getPopupContainer from ConfigProvider 1`] =
class="ant-table-filter-dropdown"
>
<ul
class="ant-dropdown-menu ant-dropdown-menu-light ant-dropdown-menu-root ant-dropdown-menu-vertical"
class="ant-dropdown-menu ant-dropdown-menu-root ant-dropdown-menu-vertical ant-dropdown-menu-light"
data-menu-list="true"
role="menu"
tabindex="0"
>
<li
class="ant-dropdown-menu-item"
data-menu-id="rc-menu-uuid-test-boy"
role="menuitem"
tabindex="-1"
>
<label
class="ant-checkbox-wrapper"
@ -862,7 +875,9 @@ exports[`Table.filter should support getPopupContainer from ConfigProvider 1`] =
</li>
<li
class="ant-dropdown-menu-item"
data-menu-id="rc-menu-uuid-test-girl"
role="menuitem"
tabindex="-1"
>
<label
class="ant-checkbox-wrapper"
@ -886,13 +901,16 @@ exports[`Table.filter should support getPopupContainer from ConfigProvider 1`] =
</li>
<li
class="ant-dropdown-menu-submenu ant-dropdown-menu-submenu-vertical"
role="menuitem"
role="none"
>
<div
aria-controls="rc-menu-uuid-test-title-popup"
aria-expanded="false"
aria-haspopup="true"
class="ant-dropdown-menu-submenu-title"
role="button"
data-menu-id="rc-menu-uuid-test-title"
role="menuitem"
tabindex="-1"
title="Title"
>
Title

View File

@ -1051,25 +1051,32 @@ exports[`Table.rowSelection should support getPopupContainer 1`] = `
style="opacity: 0; pointer-events: none;"
>
<ul
class="ant-dropdown-menu ant-dropdown-menu-light ant-dropdown-menu-root ant-dropdown-menu-vertical"
class="ant-dropdown-menu ant-dropdown-menu-root ant-dropdown-menu-vertical ant-dropdown-menu-light"
data-menu-list="true"
role="menu"
tabindex="0"
>
<li
class="ant-dropdown-menu-item ant-dropdown-menu-item-only-child"
data-menu-id="rc-menu-uuid-test-all"
role="menuitem"
tabindex="-1"
>
Select all data
</li>
<li
class="ant-dropdown-menu-item ant-dropdown-menu-item-only-child"
data-menu-id="rc-menu-uuid-test-invert"
role="menuitem"
tabindex="-1"
>
Invert current page
</li>
<li
class="ant-dropdown-menu-item ant-dropdown-menu-item-only-child"
data-menu-id="rc-menu-uuid-test-none"
role="menuitem"
tabindex="-1"
>
Clear all data
</li>
@ -1384,25 +1391,32 @@ exports[`Table.rowSelection should support getPopupContainer from ConfigProvider
style="opacity: 0; pointer-events: none;"
>
<ul
class="ant-dropdown-menu ant-dropdown-menu-light ant-dropdown-menu-root ant-dropdown-menu-vertical"
class="ant-dropdown-menu ant-dropdown-menu-root ant-dropdown-menu-vertical ant-dropdown-menu-light"
data-menu-list="true"
role="menu"
tabindex="0"
>
<li
class="ant-dropdown-menu-item ant-dropdown-menu-item-only-child"
data-menu-id="rc-menu-uuid-test-all"
role="menuitem"
tabindex="-1"
>
Select all data
</li>
<li
class="ant-dropdown-menu-item ant-dropdown-menu-item-only-child"
data-menu-id="rc-menu-uuid-test-invert"
role="menuitem"
tabindex="-1"
>
Invert current page
</li>
<li
class="ant-dropdown-menu-item ant-dropdown-menu-item-only-child"
data-menu-id="rc-menu-uuid-test-none"
role="menuitem"
tabindex="-1"
>
Clear all data
</li>

View File

@ -15882,7 +15882,7 @@ exports[`renders ./components/table/demo/sticky.md correctly 1`] = `
class="ant-table-container"
>
<div
class="ant-table-header ant-table-sticky-header"
class="ant-table-header ant-table-sticky-holder"
style="overflow:hidden;top:0"
>
<table
@ -16756,6 +16756,41 @@ exports[`renders ./components/table/demo/sticky.md correctly 1`] = `
</tbody>
</table>
</div>
<div
class="ant-table-summary ant-table-sticky-holder"
style="overflow:hidden;bottom:0"
>
<table
style="table-layout:fixed;visibility:hidden"
>
<colgroup />
<tfoot
class="ant-table-summary"
>
<tr>
<td
class="ant-table-cell ant-table-cell-fix-left ant-table-cell-fix-left-last ant-table-cell-fix-sticky"
colspan="2"
style="position:sticky;left:0"
>
Fix Left
</td>
<td
class="ant-table-cell"
colspan="8"
>
Scroll Context
</td>
<td
class="ant-table-cell ant-table-cell-fix-right ant-table-cell-fix-right-first ant-table-cell-fix-sticky"
style="position:sticky;right:0"
>
Fix Right
</td>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
<ul

View File

@ -94,7 +94,25 @@ for (let i = 0; i < 100; i++) {
}
ReactDOM.render(
<Table columns={columns} dataSource={data} scroll={{ x: 1500 }} sticky />,
<Table
columns={columns}
dataSource={data}
scroll={{ x: 1500 }}
summary={pageData => (
<Table.Summary fixed>
<Table.Summary.Row>
<Table.Summary.Cell index={0} colSpan={2}>
Fix Left
</Table.Summary.Cell>
<Table.Summary.Cell index={2} colSpan={8}>
Scroll Context
</Table.Summary.Cell>
<Table.Summary.Cell index={10}>Fix Right</Table.Summary.Cell>
</Table.Summary.Row>
</Table.Summary>
)}
sticky
/>,
mountNode,
);
```

View File

@ -37,19 +37,21 @@ function renderFilterItems({
// wrapped with <div /> to avoid react warning
// https://github.com/ant-design/ant-design/issues/25979
return (
<div
style={{
margin: '16px 0',
}}
>
<Empty
image={Empty.PRESENTED_IMAGE_SIMPLE}
description={locale.filterEmptyText}
imageStyle={{
height: 24,
<MenuItem key="empty">
<div
style={{
margin: '16px 0',
}}
/>
</div>
>
<Empty
image={Empty.PRESENTED_IMAGE_SIMPLE}
description={locale.filterEmptyText}
imageStyle={{
height: 24,
}}
/>
</div>
</MenuItem>
);
}
return filters.map((filter, index) => {

View File

@ -148,9 +148,9 @@ One of the Table `columns` prop for describing the table's columns, Column has t
### ColumnGroup
| Property | Description | Type | Default |
| --- | --- | --- | --- |
| title | Title of the column group | ReactNode | - |
| Property | Description | Type | Default |
| -------- | ------------------------- | --------- | ------- |
| title | Title of the column group | ReactNode | - |
### pagination
@ -166,22 +166,27 @@ More about pagination, please check [`Pagination`](/components/pagination/).
Properties for expandable.
| Property | Description | Type | Default |
| --- | --- | --- | --- |
| childrenColumnName | The column contains children to display | string | children |
| columnWidth | Set the width of the expand column | string \| number | - |
| defaultExpandAllRows | Expand all rows initially | boolean | false |
| defaultExpandedRowKeys | Initial expanded row keys | string\[] | - |
| 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 | - |
| 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 | - |
| expandRowByClick | Whether to expand row by clicking anywhere in the whole row | boolean | false |
| indentSize | Indent size in pixels of tree data | number | 15 |
| rowExpandable | Enable row can be expandable | (record) => boolean | - |
| onExpand | Callback executed when the row expand icon is clicked | function(expanded, record) | - |
| onExpandedRowsChange | Callback executed when the expanded rows change | function(expandedRows) | - |
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| childrenColumnName | The column contains children to display | string | children | |
| columnWidth | Set the width of the expand column | string \| number | - | |
| defaultExpandAllRows | Expand all rows initially | boolean | false | |
| defaultExpandedRowKeys | Initial expanded row keys | string\[] | - | |
| 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 | - | |
| 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 | - | |
| expandRowByClick | Whether to expand row by clicking anywhere in the whole row | boolean | false | |
| fixed | Whether the expansion icon is fixed. Optional true `left` `right` | boolean \| string | false | 4.16.0 |
| indentSize | Indent size in pixels of tree data | number | 15 | |
| rowExpandable | Enable row can be expandable | (record) => boolean | - | |
| onExpand | Callback executed when the row expand icon is clicked | function(expanded, record) | - | |
| onExpandedRowsChange | Callback executed when the expanded rows change | function(expandedRows) | - | |
- `fixed`
- When set to true or `left` and `expandIconColumnIndex` is not set or is 0, enable fixed
- When set to true or `right` and `expandIconColumnIndex` is set to the number of table columns, enable fixed
### rowSelection

View File

@ -173,22 +173,27 @@ const columns = [
展开功能的配置。
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| childrenColumnName | 指定树形结构的列名 | string | children |
| columnWidth | 自定义展开列宽度 | string \| number | - |
| defaultExpandAllRows | 初始时,是否展开所有行 | boolean | false |
| defaultExpandedRowKeys | 默认展开的行 | string\[] | - |
| expandedRowClassName | 展开行的 className | function(record, index, indent): string | - |
| expandedRowKeys | 展开的行,控制属性 | string\[] | - |
| expandedRowRender | 额外的展开行 | function(record, index, indent, expanded): ReactNode | - |
| expandIcon | 自定义展开图标,参考[示例](https://codesandbox.io/s/fervent-bird-nuzpr) | function(props): ReactNode | - |
| expandIconColumnIndex | 自定义展开按钮的列顺序,`-1` 时不展示 | number | - |
| expandRowByClick | 通过点击行来展开子行 | boolean | false |
| indentSize | 展示树形数据时,每层缩进的宽度,以 px 为单位 | number | 15 |
| rowExpandable | 设置是否允许行展开 | (record) => boolean | - |
| onExpand | 点击展开图标时触发 | function(expanded, record) | - |
| onExpandedRowsChange | 展开的行变化时触发 | function(expandedRows) | - |
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| childrenColumnName | 指定树形结构的列名 | string | children | |
| columnWidth | 自定义展开列宽度 | string \| number | - | |
| defaultExpandAllRows | 初始时,是否展开所有行 | boolean | false | |
| defaultExpandedRowKeys | 默认展开的行 | string\[] | - | |
| expandedRowClassName | 展开行的 className | function(record, index, indent): string | - | |
| expandedRowKeys | 展开的行,控制属性 | string\[] | - | |
| expandedRowRender | 额外的展开行 | function(record, index, indent, expanded): ReactNode | - | |
| expandIcon | 自定义展开图标,参考[示例](https://codesandbox.io/s/fervent-bird-nuzpr) | function(props): ReactNode | - | |
| expandIconColumnIndex | 自定义展开按钮的列顺序,`-1` 时不展示 | number | - | |
| expandRowByClick | 通过点击行来展开子行 | boolean | false | |
| fixed | 控制展开图标是否固定,可选 true `left` `right` | boolean \| string | false | 4.16.0 |
| indentSize | 展示树形数据时,每层缩进的宽度,以 px 为单位 | number | 15 | |
| rowExpandable | 设置是否允许行展开 | (record) => boolean | - | |
| onExpand | 点击展开图标时触发 | function(expanded, record) | - | |
| onExpandedRowsChange | 展开的行变化时触发 | function(expandedRows) | - | |
- `fixed`
- 当设置为 true 或 `left``expandIconColumnIndex` 未设置或为 0 时,开启固定
- 当设置为 true 或 `right``expandIconColumnIndex` 设置为表格列数时,开启固定
### rowSelection

View File

@ -9,7 +9,7 @@
@table-header-icon-color: #bfbfbf;
@table-header-icon-color-hover: darken(@table-header-icon-color, 10%);
@table-header-sort-active-filter-bg: lighten(@table-header-sort-active-bg, 2%);
@table-sticky-zindex: calc(@zindex-table-fixed + 1);
@table-sticky-zindex: (@zindex-table-fixed + 1);
@table-sticky-scroll-bar-active-bg: fade(@table-sticky-scroll-bar-bg, 80%);
.@{table-prefix-cls}-wrapper {
@ -147,7 +147,13 @@
}
// =========================== Summary ============================
tfoot {
&-summary {
background: @table-bg;
div& {
box-shadow: 0 -@border-width-base 0 @table-border-color;
}
> tr {
> th,
> td {
@ -622,7 +628,7 @@
}
}
&-sticky {
&-header {
&-holder {
position: sticky;
z-index: @table-sticky-zindex;
}

View File

@ -111,6 +111,242 @@ exports[`Tabs rtl render component should be rendered correctly in RTL direction
</div>
`;
exports[`Tabs tabBarGutter should work 1`] = `
<div
class="ant-tabs ant-tabs-top"
>
<div
class="ant-tabs-nav"
role="tablist"
>
<div
class="ant-tabs-nav-wrap"
>
<div
class="ant-tabs-nav-list"
style="transform:translate(0px, 0px)"
>
<div
class="ant-tabs-tab ant-tabs-tab-active"
style="margin-left:0"
>
<div
aria-selected="true"
class="ant-tabs-tab-btn"
role="tab"
tabindex="0"
/>
</div>
<div
class="ant-tabs-tab ant-tabs-tab-active"
style="margin-left:0"
>
<div
aria-selected="true"
class="ant-tabs-tab-btn"
role="tab"
tabindex="0"
/>
</div>
<div
class="ant-tabs-tab ant-tabs-tab-active"
style="margin-left:0"
>
<div
aria-selected="true"
class="ant-tabs-tab-btn"
role="tab"
tabindex="0"
/>
</div>
<div
class="ant-tabs-ink-bar ant-tabs-ink-bar-animated"
/>
</div>
</div>
<div
class="ant-tabs-nav-operations ant-tabs-nav-operations-hidden"
>
<button
aria-controls="null-more-popup"
aria-expanded="false"
aria-haspopup="listbox"
aria-hidden="true"
class="ant-tabs-nav-more"
id="null-more"
style="margin-left:0;visibility:hidden;order:1"
tabindex="-1"
type="button"
>
<span
aria-label="ellipsis"
class="anticon anticon-ellipsis"
role="img"
>
<svg
aria-hidden="true"
data-icon="ellipsis"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M176 511a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0z"
/>
</svg>
</span>
</button>
</div>
</div>
<div
class="ant-tabs-content-holder"
>
<div
class="ant-tabs-content ant-tabs-content-top"
>
<div
aria-hidden="false"
class="ant-tabs-tabpane ant-tabs-tabpane-active"
role="tabpanel"
tabindex="0"
/>
<div
aria-hidden="false"
class="ant-tabs-tabpane ant-tabs-tabpane-active"
role="tabpanel"
tabindex="0"
/>
<div
aria-hidden="false"
class="ant-tabs-tabpane ant-tabs-tabpane-active"
role="tabpanel"
tabindex="0"
/>
</div>
</div>
</div>
`;
exports[`Tabs tabBarGutter should work 2`] = `
<div
class="ant-tabs ant-tabs-left"
>
<div
class="ant-tabs-nav"
role="tablist"
>
<div
class="ant-tabs-nav-wrap"
>
<div
class="ant-tabs-nav-list"
style="transform:translate(0px, 0px)"
>
<div
class="ant-tabs-tab ant-tabs-tab-active"
style="margin-top:0"
>
<div
aria-selected="true"
class="ant-tabs-tab-btn"
role="tab"
tabindex="0"
/>
</div>
<div
class="ant-tabs-tab ant-tabs-tab-active"
style="margin-top:0"
>
<div
aria-selected="true"
class="ant-tabs-tab-btn"
role="tab"
tabindex="0"
/>
</div>
<div
class="ant-tabs-tab ant-tabs-tab-active"
style="margin-top:0"
>
<div
aria-selected="true"
class="ant-tabs-tab-btn"
role="tab"
tabindex="0"
/>
</div>
<div
class="ant-tabs-ink-bar ant-tabs-ink-bar-animated"
/>
</div>
</div>
<div
class="ant-tabs-nav-operations ant-tabs-nav-operations-hidden"
>
<button
aria-controls="null-more-popup"
aria-expanded="false"
aria-haspopup="listbox"
aria-hidden="true"
class="ant-tabs-nav-more"
id="null-more"
style="margin-left:0;visibility:hidden;order:1"
tabindex="-1"
type="button"
>
<span
aria-label="ellipsis"
class="anticon anticon-ellipsis"
role="img"
>
<svg
aria-hidden="true"
data-icon="ellipsis"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M176 511a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0z"
/>
</svg>
</span>
</button>
</div>
</div>
<div
class="ant-tabs-content-holder"
>
<div
class="ant-tabs-content ant-tabs-content-left"
>
<div
aria-hidden="false"
class="ant-tabs-tabpane ant-tabs-tabpane-active"
role="tabpanel"
tabindex="0"
/>
<div
aria-hidden="false"
class="ant-tabs-tabpane ant-tabs-tabpane-active"
role="tabpanel"
tabindex="0"
/>
<div
aria-hidden="false"
class="ant-tabs-tabpane ant-tabs-tabpane-active"
role="tabpanel"
tabindex="0"
/>
</div>
</div>
</div>
`;
exports[`Tabs tabPosition remove card 1`] = `
<div
class="ant-tabs ant-tabs-left"

View File

@ -85,4 +85,11 @@ describe('Tabs', () => {
);
errorSpy.mockRestore();
});
it('tabBarGutter should work', () => {
const wrapper = mount(<Tabs tabBarGutter={0}><TabPane /><TabPane /><TabPane /></Tabs>);
expect(wrapper).toMatchRenderedSnapshot();
const wrapper2 = mount(<Tabs tabBarGutter={0} tabPosition="left"><TabPane /><TabPane /><TabPane /></Tabs>);
expect(wrapper2).toMatchRenderedSnapshot();
});
});

View File

@ -2,6 +2,7 @@ import { TimePickerLocale } from '../index';
const locale: TimePickerLocale = {
placeholder: 'Selecteer tijd',
rangePlaceholder: ['Start tijd', 'Eind tijd'],
};
export default locale;

View File

@ -2,6 +2,7 @@ import { TimePickerLocale } from '../index';
const locale: TimePickerLocale = {
placeholder: 'Selecteer tijd',
rangePlaceholder: ['Start tijd', 'Eind tijd'],
};
export default locale;

View File

@ -67,10 +67,11 @@ export interface BlockProps extends TypographyProps {
delete?: boolean;
strong?: boolean;
keyboard?: boolean;
italic?: boolean;
}
function wrapperDecorations(
{ mark, code, underline, delete: del, strong, keyboard }: BlockProps,
{ mark, code, underline, delete: del, strong, keyboard, italic }: BlockProps,
content: React.ReactNode,
) {
let currentContent = content;
@ -87,6 +88,7 @@ function wrapperDecorations(
wrap(code, 'code');
wrap(mark, 'mark');
wrap(keyboard, 'kbd');
wrap(italic, 'i');
return currentContent;
}

View File

@ -997,6 +997,18 @@ exports[`renders ./components/typography/demo/text.md correctly 1`] = `
</strong>
</span>
</div>
<div
class="ant-space-item"
style="margin-bottom:8px"
>
<span
class="ant-typography"
>
<i>
Ant Design (italic)
</i>
</span>
</div>
<div
class="ant-space-item"
>

View File

@ -32,6 +32,7 @@ ReactDOM.render(
<Text underline>Ant Design (underline)</Text>
<Text delete>Ant Design (delete)</Text>
<Text strong>Ant Design (strong)</Text>
<Text italic>Ant Design (italic)</Text>
<Link href="https://ant.design" target="_blank">
Ant Design (Link)
</Link>

View File

@ -29,6 +29,7 @@ Basic text writing, including headings, body text, lists, and more.
| mark | Marked style | boolean | false | |
| onClick | Set the handler to handle click event | (event) => void | - | |
| strong | Bold style | boolean | false | |
| italic | Italic style | boolean | false | 4.16.0 |
| type | Content type | `secondary` \| `success` \| `warning` \| `danger` | - | success: 4.6.0 |
| underline | Underlined style | boolean | false | |
@ -45,6 +46,7 @@ Basic text writing, including headings, body text, lists, and more.
| level | Set content importance. Match with `h1`, `h2`, `h3`, `h4`, `h5` | number: 1, 2, 3, 4, 5 | 1 | 5: 4.6.0 |
| mark | Marked style | boolean | false | |
| onClick | Set the handler to handle click event | (event) => void | - | |
| italic | Italic style | boolean | false | 4.16.0 |
| type | Content type | `secondary` \| `success` \| `warning` \| `danger` | - | success: 4.6.0 |
| underline | Underlined style | boolean | false | |
@ -61,6 +63,7 @@ Basic text writing, including headings, body text, lists, and more.
| mark | Marked style | boolean | false | |
| onClick | Set the handler to handle click event | (event) => void | - | |
| strong | Bold style | boolean | false | |
| italic | Italic style | boolean | false | 4.16.0 |
| type | Content type | `secondary` \| `success` \| `warning` \| `danger` | - | success: 4.6.0 |
| underline | Underlined style | boolean | false | |

View File

@ -30,6 +30,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/GOM1KQ24O/Typography.svg
| mark | 添加标记样式 | boolean | false | |
| onClick | 点击 Text 时的回调 | (event) => void | - | |
| strong | 是否加粗 | boolean | false | |
| italic | 是否斜体 | boolean | false | 4.16.0 |
| type | 文本类型 | `secondary` \| `success` \| `warning` \| `danger` | - | success: 4.6.0 |
| underline | 添加下划线样式 | boolean | false | |
@ -46,6 +47,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/GOM1KQ24O/Typography.svg
| level | 重要程度,相当于 `h1`、`h2`、`h3`、`h4`、`h5` | number: 1, 2, 3, 4, 5 | 1 | 5: 4.6.0 |
| mark | 添加标记样式 | boolean | false | |
| onClick | 点击 Title 时的回调 | (event) => void | - | |
| italic | 是否斜体 | boolean | false | 4.16.0 |
| type | 文本类型 | `secondary` \| `success` \| `warning` \| `danger` | - | success: 4.6.0 |
| underline | 添加下划线样式 | boolean | false | |
@ -62,6 +64,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/GOM1KQ24O/Typography.svg
| mark | 添加标记样式 | boolean | false | |
| onClick | 点击 Paragraph 时的回调 | (event) => void | - | |
| strong | 是否加粗 | boolean | false | |
| italic | 是否斜体 | boolean | false | 4.16.0 |
| type | 文本类型 | `secondary` \| `success` \| `warning` \| `danger` | - | success: 4.6.0 |
| underline | 添加下划线样式 | boolean | false | |

View File

@ -34,6 +34,7 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
onPreview,
onDownload,
onChange,
onDrop,
previewFile,
disabled,
locale: propLocale,
@ -278,8 +279,11 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
};
const onFileDrop = (e: React.DragEvent<HTMLDivElement>) => {
e.stopPropagation();
setDragState(e.type);
if (e.type === 'drop') {
onDrop?.(e);
}
};
// Test needs

View File

@ -272,7 +272,13 @@ const ListItem = React.forwardRef(
return (
<div className={listContainerNameClass} style={style} ref={ref}>
{itemRender ? itemRender(item, file, items) : item}
{itemRender
? itemRender(item, file, items, {
download: onDownload.bind(null, file),
preview: onPreview.bind(null, file),
remove: onClose.bind(null, file),
})
: item}
</div>
);
},

View File

@ -80,11 +80,11 @@ const InternalUploadList: React.ForwardRefRenderFunction<unknown, UploadListProp
}, []);
// ============================= Events =============================
const onInternalPreview = (file: UploadFile, e: React.SyntheticEvent<HTMLElement>) => {
const onInternalPreview = (file: UploadFile, e?: React.SyntheticEvent<HTMLElement>) => {
if (!onPreview) {
return;
}
e.preventDefault();
e?.preventDefault();
return onPreview(file);
};

View File

@ -114,20 +114,54 @@ exports[`Upload List itemRender 1`] = `
<div
class="ant-upload-list-text-container"
>
<span
<div
class="custom-item-render"
>
uid:-1 name: xxx.png status: removed url: https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png 1/2
</span>
<span>
uid:-1 name: xxx.png status: removed url: https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png 1/2
</span>
<span
class="custom-item-render-action-remove"
>
remove
</span>
<span
class="custom-item-render-action-download"
>
download
</span>
<span
class="custom-item-render-action-preview"
>
preview
</span>
</div>
</div>
<div
class="ant-upload-list-text-container"
>
<span
<div
class="custom-item-render"
>
uid:-2 name: yyy.png status: removed url: https://zos.alipayobjects.com/rmsportal/IQKRngzUuFzJzGzRJXUs.png 2/2
</span>
<span>
uid:-2 name: yyy.png status: removed url: https://zos.alipayobjects.com/rmsportal/IQKRngzUuFzJzGzRJXUs.png 2/2
</span>
<span
class="custom-item-render-action-remove"
>
remove
</span>
<span
class="custom-item-render-action-download"
>
download
</span>
<span
class="custom-item-render-action-preview"
>
preview
</span>
</div>
</div>
</div>
`;

View File

@ -35,4 +35,21 @@ describe('Upload.Dragger', () => {
jest.useRealTimers();
});
it('support onDrop when files are dropped onto upload area', () => {
const onDrop = jest.fn();
const wrapper = mount(
<Upload.Dragger onDrop={onDrop}>
<div />
</Upload.Dragger>,
);
wrapper.find('.ant-upload-drag-container').simulate('drop', {
dataTransfer: {
files: [new File(['foo'], 'foo.png', { type: 'image/png' })],
},
});
expect(onDrop).toHaveBeenCalled();
});
});

View File

@ -100,7 +100,24 @@ describe('Upload.typescript', () => {
},
];
const upload = (
<Upload fileList={fileList} defaultFileList={fileList}>
<Upload fileList={fileList} defaultFileList={fileList} />
)
expect(upload).toBeTruthy();
});
it('itemRender', () => {
const upload = (
<Upload
itemRender={(node, file, list, actions) => (
<div>
{node}
{file.name}
{list.length}
<span onClick={actions.remove}>remove</span>
<span onClick={actions.download}>download</span>
<span onClick={actions.preview}>preview</span>
</div>
)}
>
<span>click to upload</span>
</Upload>
);

View File

@ -1162,20 +1162,52 @@ describe('Upload List', () => {
});
it('itemRender', () => {
const itemRender = (originNode, file, currFileList) => {
const onDownload = jest.fn();
const onRemove = jest.fn();
const onPreview = jest.fn();
const itemRender = (originNode, file, currFileList, actions) => {
const { name, status, uid, url } = file;
const index = currFileList.indexOf(file);
return (
<span className="custom-item-render">
{`uid:${uid} name: ${name} status: ${status} url: ${url} ${index + 1}/${
currFileList.length
}`}
</span>
<div className="custom-item-render">
<span>
{`uid:${uid} name: ${name} status: ${status} url: ${url} ${index + 1}/${
currFileList.length
}`}
</span>
<span onClick={actions.remove} className="custom-item-render-action-remove">
remove
</span>
<span onClick={actions.download} className="custom-item-render-action-download">
download
</span>
<span onClick={actions.preview} className="custom-item-render-action-preview">
preview
</span>
</div>
);
};
const wrapper = mount(<UploadList locale={{}} items={fileList} itemRender={itemRender} />);
const wrapper = mount(
<UploadList
onDownload={onDownload}
onPreview={onPreview}
onRemove={onRemove}
locale={{}}
items={fileList}
itemRender={itemRender}
/>,
);
expect(wrapper.render()).toMatchSnapshot();
wrapper.find('.custom-item-render-action-remove').first().simulate('click');
expect(onRemove.mock.calls[0][0]).toEqual(fileList[0]);
wrapper.find('.custom-item-render-action-download').first().simulate('click');
expect(onDownload.mock.calls[0][0]).toEqual(fileList[0]);
wrapper.find('.custom-item-render-action-preview').first().simulate('click');
expect(onPreview.mock.calls[0][0]).toEqual(fileList[0]);
wrapper.unmount();
});

View File

@ -38,6 +38,9 @@ const props = {
message.error(`${info.file.name} file upload failed.`);
}
},
onDrop(e) {
console.log('Dropped files', e.dataTransfer.files);
},
};
ReactDOM.render(

View File

@ -31,7 +31,7 @@ Uploading is the process of publishing information (web pages, text, pictures, v
| headers | Set request headers, valid above IE10 | object | - | |
| iconRender | Custom show icon | (file: UploadFile, listType?: UploadListType) => ReactNode | - | |
| isImageUrl | Customize if render &lt;img /> in thumbnail | (file: UploadFile) => boolean | [(inside implementation)](https://github.com/ant-design/ant-design/blob/4ad5830eecfb87471cd8ac588c5d992862b70770/components/upload/utils.tsx#L47-L68) | |
| itemRender | Custom item of uploadList | (originNode: ReactElement, file: UploadFile, fileList?: object\[]) => React.ReactNode | - | 4.7.0 |
| itemRender | Custom item of uploadList | (originNode: ReactElement, file: UploadFile, fileList: object\[], actions: { download: function, preview: function, remove: function }) => React.ReactNode | - | 4.16.0 |
| listType | Built-in stylesheets, support for three types: `text`, `picture` or `picture-card` | string | `text` | |
| maxCount | Limit the number of uploaded files. Will replace current one when `maxCount` is `1` | number | - | 4.10.0 |
| method | The http method of upload request | string | `post` | |
@ -43,6 +43,7 @@ Uploading is the process of publishing information (web pages, text, pictures, v
| showUploadList | Whether to show default upload list, could be an object to specify `showPreviewIcon`, `showRemoveIcon`, `showDownloadIcon`, `removeIcon` and `downloadIcon` individually | boolean \| { showPreviewIcon?: boolean, showDownloadIcon?: boolean, showRemoveIcon?: boolean, removeIcon?: ReactNode \| (file: UploadFile) => ReactNode, downloadIcon?: ReactNode \| (file: UploadFile) => ReactNode } | true | function: 4.7.0 |
| withCredentials | The ajax upload with cookie sent | boolean | false | |
| onChange | A callback function, can be executed when uploading state is changing, see [onChange](#onChange) | function | - | |
| onDrop | A callback function executed when files are dragged and dropped into upload area | (event: React.DragEvent) => void | - | 4.16.0 |
| onDownload | Click the method to download the file, pass the method to perform the method logic, do not pass the default jump to the new TAB | function(file): void | (Jump to new TAB) | |
| onPreview | A callback function, will be executed when file link or preview icon is clicked | function(file) | - | |
| onRemove | A callback function, will be executed when removing file button is clicked, remove event will be prevented when return value is false or a Promise which resolve(false) or reject | function(file): boolean \| Promise | - | |

View File

@ -32,7 +32,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/QaeBt_ZMg/Upload.svg
| headers | 设置上传的请求头部IE10 以上有效 | object | - | |
| iconRender | 自定义显示 icon | (file: UploadFile, listType?: UploadListType) => ReactNode | - | |
| isImageUrl | 自定义缩略图是否使用 &lt;img /> 标签进行显示 | (file: UploadFile) => boolean | [(内部实现)](https://github.com/ant-design/ant-design/blob/4ad5830eecfb87471cd8ac588c5d992862b70770/components/upload/utils.tsx#L47-L68) | |
| itemRender | 自定义上传列表项 | (originNode: ReactElement, file: UploadFile, fileList?: object\[]) => React.ReactNode | - | 4.7.0 |
| itemRender | 自定义上传列表项 | (originNode: ReactElement, file: UploadFile, fileList: object\[], actions: { download: function, preview: function, remove: function }) => React.ReactNode | - | 4.16.0 |
| listType | 上传列表的内建样式,支持三种基本样式 `text`, `picture``picture-card` | string | `text` | |
| maxCount | 限制上传数量。当为 1 时,始终用最新上传的文件代替当前文件 | number | - | 4.10.0 |
| method | 上传请求的 http method | string | `post` | |
@ -44,6 +44,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/QaeBt_ZMg/Upload.svg
| showUploadList | 是否展示文件列表, 可设为一个对象,用于单独设定 `showPreviewIcon`, `showRemoveIcon`, `showDownloadIcon`, `removeIcon``downloadIcon` | boolean \| { showPreviewIcon?: boolean, showRemoveIcon?: boolean, showDownloadIcon?: boolean, removeIcon?: ReactNode \| (file: UploadFile) => ReactNode, downloadIcon?: ReactNode \| (file: UploadFile) => ReactNode } | true | function: 4.7.0 |
| withCredentials | 上传请求时是否携带 cookie | boolean | false | |
| onChange | 上传文件改变时的状态,详见 [onChange](#onChange) | function | - | |
| onDrop | 当文件被拖入上传区域时执行的回调功能 | (event: React.DragEvent) => void | - | 4.16.0 |
| onDownload | 点击下载文件时的回调,如果没有指定,则默认跳转到文件 url 对应的标签页 | function(file): void | (跳转新标签页) | |
| onPreview | 点击文件链接或预览图标时的回调 | function(file) | - | |
| onRemove   | 点击移除文件时的回调,返回值为 false 时不移除。支持返回一个 Promise 对象Promise 对象 resolve(false) 或 reject 时不移除               | function(file): boolean \| Promise | -   | |

View File

@ -69,7 +69,12 @@ export type UploadListProgressProps = Omit<ProgressProps, 'percent' | 'type'>;
export type ItemRender<T = any> = (
originNode: React.ReactElement,
file: UploadFile,
fileList?: Array<UploadFile<T>>,
fileList: Array<UploadFile<T>>,
actions: {
download: () => void;
preview: () => void;
remove: () => void;
},
) => React.ReactNode;
type PreviewFileHandler = (file: File | Blob) => PromiseLike<string>;
@ -96,6 +101,7 @@ export interface UploadProps<T = any> {
FileList: RcFile[],
) => BeforeUploadValueType | Promise<BeforeUploadValueType>;
onChange?: (info: UploadChangeParam) => void;
onDrop?: (event: React.DragEvent<HTMLDivElement>) => void;
listType?: UploadListType;
className?: string;
onPreview?: (file: UploadFile<T>) => void;

View File

@ -125,8 +125,8 @@
"rc-field-form": "~1.20.0",
"rc-image": "~5.2.4",
"rc-input-number": "~7.1.0",
"rc-mentions": "~1.5.0",
"rc-menu": "~8.10.0",
"rc-mentions": "~1.6.0",
"rc-menu": "~9.0.0-alpha.6",
"rc-motion": "^2.4.0",
"rc-notification": "~4.5.2",
"rc-pagination": "~3.1.6",
@ -138,8 +138,8 @@
"rc-slider": "~9.7.1",
"rc-steps": "~4.1.0",
"rc-switch": "~3.2.0",
"rc-table": "~7.13.0",
"rc-tabs": "~11.7.0",
"rc-table": "~7.15.1",
"rc-tabs": "~11.9.0",
"rc-textarea": "~0.3.0",
"rc-tooltip": "~5.1.1",
"rc-tree": "~4.1.0",

View File

@ -24,7 +24,9 @@ async function checkVersion() {
}
async function checkBranch({ current }) {
if (current !== 'master' && current !== '4.0-prepare') {
if (version.includes('-alpha.')) {
console.log(chalk.cyan('😃 Alpha version. Skip branch check.'));
} else if (current !== 'master' && current !== '4.0-prepare') {
console.log(chalk.yellow('🤔 You are not in the master branch!'));
exitProcess();
}