Merge pull request #32036 from ant-design/master

branch: merge master into feature
This commit is contained in:
xrkffgg 2021-09-06 15:25:15 +08:00 committed by GitHub
commit 3d81a10d61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 173 additions and 32 deletions

View File

@ -12,7 +12,7 @@ jobs:
steps:
- name: remove inactive
if: github.event.issue.state == 'open'
uses: actions-cool/issues-helper@v1.2
uses: actions-cool/issues-helper@v2.4.1
with:
actions: 'remove-labels'
issue-number: ${{ github.event.issue.number }}

View File

@ -2,7 +2,7 @@ name: PR Open Check
on:
pull_request_target:
types: [opened, edited, reopened]
types: [opened, edited, reopened, synchronize]
jobs:
refuse:

View File

@ -0,0 +1,27 @@
import * as React from 'react';
import DatePicker from '..';
describe('DatePicker.typescript', () => {
it('DatePicker ref methods', () => {
const datePicker = (
<DatePicker
ref={picker => {
picker?.focus();
picker?.blur();
}}
/>
);
expect(datePicker).toBeTruthy();
});
it('RangePicker ref methods', () => {
const rangePicker = (
<DatePicker.RangePicker
ref={picker => {
picker?.focus();
picker?.blur();
}}
/>
);
expect(rangePicker).toBeTruthy();
});
});

View File

@ -12,10 +12,11 @@ import SizeContext from '../../config-provider/SizeContext';
import LocaleReceiver from '../../locale-provider/LocaleReceiver';
import { getRangePlaceholder } from '../util';
import { RangePickerProps, PickerLocale, getTimeProps, Components } from '.';
import { PickerComponentClass } from './interface';
export default function generateRangePicker<DateType>(
generateConfig: GenerateConfig<DateType>,
): React.ComponentClass<RangePickerProps<DateType>> {
): PickerComponentClass<RangePickerProps<DateType>> {
class RangePicker extends React.Component<RangePickerProps<DateType>> {
static contextType = ConfigContext;

View File

@ -20,6 +20,7 @@ import {
getTimeProps,
Components,
} from '.';
import { PickerComponentClass } from './interface';
export default function generatePicker<DateType>(generateConfig: GenerateConfig<DateType>) {
type DatePickerProps = PickerProps<DateType>;
@ -147,7 +148,7 @@ export default function generatePicker<DateType>(generateConfig: GenerateConfig<
Picker.displayName = displayName;
}
return Picker as React.ComponentClass<InnerPickerProps>;
return Picker as PickerComponentClass<InnerPickerProps>;
}
const DatePicker = getPicker<DatePickerProps>();

View File

@ -1,4 +1,3 @@
import * as React from 'react';
import { GenerateConfig } from 'rc-picker/lib/generate/index';
import {
PickerBaseProps as RCPickerBaseProps,
@ -124,14 +123,8 @@ export type RangePickerProps<DateType> =
function generatePicker<DateType>(generateConfig: GenerateConfig<DateType>) {
// =========================== Picker ===========================
const {
DatePicker,
WeekPicker,
MonthPicker,
YearPicker,
TimePicker,
QuarterPicker,
} = generateSinglePicker(generateConfig);
const { DatePicker, WeekPicker, MonthPicker, YearPicker, TimePicker, QuarterPicker } =
generateSinglePicker(generateConfig);
// ======================== Range Picker ========================
const RangePicker = generateRangePicker(generateConfig);
@ -141,7 +134,7 @@ function generatePicker<DateType>(generateConfig: GenerateConfig<DateType>) {
WeekPicker: typeof WeekPicker;
MonthPicker: typeof MonthPicker;
YearPicker: typeof YearPicker;
RangePicker: React.ComponentClass<RangePickerProps<DateType>>;
RangePicker: typeof RangePicker;
TimePicker: typeof TimePicker;
QuarterPicker: typeof QuarterPicker;
};

View File

@ -0,0 +1,11 @@
import { ComponentClass } from 'react';
export interface CommonPickerMethods {
focus: () => void;
blur: () => void;
}
export interface PickerComponentClass<P = {}, S = unknown> extends ComponentClass<P, S> {
new (...args: ConstructorParameters<ComponentClass<P, S>>): InstanceType<ComponentClass<P, S>> &
CommonPickerMethods;
}

View File

@ -19,6 +19,8 @@ type EventType =
type getContainerFunc = () => HTMLElement;
type ILevelMove = number | [number, number];
const PlacementTypes = tuple('top', 'right', 'bottom', 'left');
type placementType = typeof PlacementTypes[number];
@ -62,6 +64,9 @@ export interface DrawerProps {
footer?: React.ReactNode;
footerStyle?: React.CSSProperties;
level?: string | string[] | null | undefined;
levelMove?:
| ILevelMove
| ((e: { target: HTMLElement; open: boolean }) => ILevelMove);
}
export interface IDrawerState {

View File

@ -27,7 +27,7 @@ When there are more than a few options to choose from, you can wrap them in a `D
| placement | Placement of popup menu: `bottomLeft`, `bottomCenter`, `bottomRight`, `topLeft`, `topCenter` or `topRight` | string | `bottomLeft` | |
| trigger | The trigger mode which executes the dropdown action. Note that hover can't be used on touchscreens | Array&lt;`click`\|`hover`\|`contextMenu`> | \[`hover`] | |
| visible | Whether the dropdown menu is currently visible | boolean | - | |
| onVisibleChange | Called when the visible state is changed | (visible: boolean) => void | - | |
| onVisibleChange | Called when the visible state is changed. Not trigger when hidden by click item | (visible: boolean) => void | - | |
You should use [Menu](/components/menu/) as `overlay`. The menu items and dividers are also available by using `Menu.Item` and `Menu.Divider`.

View File

@ -31,7 +31,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/eedWN59yJ/Dropdown.svg
| placement | 菜单弹出位置:`bottomLeft` `bottomCenter` `bottomRight` `topLeft` `topCenter` `topRight` | string | `bottomLeft` | |
| trigger | 触发下拉的行为, 移动端不支持 hover | Array&lt;`click`\|`hover`\|`contextMenu`> | \[`hover`] | |
| visible | 菜单是否显示 | boolean | - | |
| onVisibleChange | 菜单显示状态改变时调用,参数为 `visible` | (visible: boolean) => void | - | |
| onVisibleChange | 菜单显示状态改变时调用,参数为 `visible`。点击菜单按钮导致的消失不会触发 | (visible: boolean) => void | - | |
`overlay` 菜单使用 [Menu](/components/menu/),还包括菜单项 `Menu.Item`,分割线 `Menu.Divider`

View File

@ -119,6 +119,9 @@ const localeValues: Locale = {
},
},
},
Image: {
preview: 'Vorschau',
},
};
export default localeValues;

View File

@ -441,6 +441,76 @@ describe('Table.rowSelection', () => {
expect(handleSelectEven).toHaveBeenCalledWith([0, 1, 2, 3]);
});
describe('preset selection options', () => {
const presetData = [
{ key: 0, name: 'Jack' },
{ key: 1, name: 'Lucy', disabled: true },
{ key: 2, name: 'Tom' },
];
const getCheckboxProps = record => record;
it('SELECTION_ALL', () => {
const onChange = jest.fn();
const wrapper = mount(
createTable({
dataSource: presetData,
rowSelection: {
onChange,
defaultSelectedRowKeys: [2],
getCheckboxProps,
selections: [Table.SELECTION_ALL],
},
}),
);
wrapper.find('Trigger').setState({ popupVisible: true });
wrapper.find('li.ant-dropdown-menu-item').first().simulate('click');
expect(onChange).toHaveBeenCalledWith([0, 2], expect.anything());
});
it('SELECTION_INVERT', () => {
const onChange = jest.fn();
const wrapper = mount(
createTable({
dataSource: presetData,
rowSelection: {
onChange,
defaultSelectedRowKeys: [2],
getCheckboxProps,
selections: [Table.SELECTION_INVERT],
},
}),
);
wrapper.find('Trigger').setState({ popupVisible: true });
wrapper.find('li.ant-dropdown-menu-item').first().simulate('click');
expect(onChange).toHaveBeenCalledWith([0], expect.anything());
});
it('SELECTION_NONE', () => {
const onChange = jest.fn();
const wrapper = mount(
createTable({
dataSource: presetData,
rowSelection: {
onChange,
defaultSelectedRowKeys: [1, 2],
getCheckboxProps,
selections: [Table.SELECTION_NONE],
},
}),
);
wrapper.find('Trigger').setState({ popupVisible: true });
wrapper.find('li.ant-dropdown-menu-item').first().simulate('click');
expect(onChange).toHaveBeenCalledWith([1], expect.anything());
});
});
it('could hide selectAll checkbox and custom selection', () => {
const rowSelection = {
hideSelectAll: true,

View File

@ -79,7 +79,7 @@ class SortableTable extends React.Component {
onSortEnd = ({ oldIndex, newIndex }) => {
const { dataSource } = this.state;
if (oldIndex !== newIndex) {
const newData = arrayMove([].concat(dataSource), oldIndex, newIndex).filter(el => !!el);
const newData = arrayMoveImmutable([].concat(dataSource), oldIndex, newIndex).filter(el => !!el);
console.log('Sorted items: ', newData);
this.setState({ dataSource: newData });
}

View File

@ -281,7 +281,14 @@ export default function useSelection<RecordType>(
key: 'all',
text: tableLocale.selectionAll,
onSelect() {
setSelectedKeys(data.map((record, index) => getRowKey(record, index)));
setSelectedKeys(
data
.map((record, index) => getRowKey(record, index))
.filter(key => {
const checkProps = checkboxPropsMap.get(key);
return !checkProps?.disabled || derivedSelectedKeySet.has(key);
}),
);
},
};
}
@ -293,11 +300,14 @@ export default function useSelection<RecordType>(
const keySet = new Set(derivedSelectedKeySet);
pageData.forEach((record, index) => {
const key = getRowKey(record, index);
const checkProps = checkboxPropsMap.get(key);
if (keySet.has(key)) {
keySet.delete(key);
} else {
keySet.add(key);
if (!checkProps?.disabled) {
if (keySet.has(key)) {
keySet.delete(key);
} else {
keySet.add(key);
}
}
});
@ -321,7 +331,12 @@ export default function useSelection<RecordType>(
text: tableLocale.selectNone,
onSelect() {
onSelectNone?.();
setSelectedKeys([]);
setSelectedKeys(
Array.from(derivedSelectedKeySet).filter(key => {
const checkProps = checkboxPropsMap.get(key);
return checkProps?.disabled;
}),
);
},
};
}

View File

@ -86,16 +86,32 @@ title: 按钮
经常独立出现,行动号召按钮就像是电脑在对用户大声说“跟我来吧”,有点命令用户点击的意味,通常出现于 landing page 或者 一些引导性场景。最大可以将按钮放宽到与父区域等宽。一个屏幕空间中,建议只有一个行动号召按钮。
## 位置
### 按钮区
<img class="preview-img no-padding" align="right" src="https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*B8D0RJnirLkAAAAAAAAAAABkARQnAQ">
<img class="preview-img no-padding" align="right" src="https://img.alicdn.com/imgextra/i1/O1CN01Wd9Dbh1z6A5MQwEnh_!!6000000006664-2-tps-930-290.png">
按钮区是用于放置按钮的区域,一个按钮区内可以有多个按钮。
### 跟随内容的按钮区
<img class="preview-img no-padding" align="right" src="https://img.alicdn.com/imgextra/i4/O1CN01OVOv5G27z8YLYdWED_!!6000000007867-2-tps-928-342.png">
按钮区跟随受控内容。将按钮区放置于用户浏览路径中,便于被用户发现。
### 工具栏中的按钮区
<img class="preview-img no-padding" align="right" src="https://img.alicdn.com/imgextra/i2/O1CN01aAZHoi1uZrgx1C3zR_!!6000000006052-2-tps-928-332.png">
工具栏中的按钮区,靠右放置。控制工具栏控制的内容范围。
将按钮区放置于用户浏览路径中,便于被用户发现,如 “F 浏览模式” 和 “Z 浏览模式” 。
### 如何确定按钮区的放置位置?
#### 页面/卡片/一组信息都能够呈现一个主题,主题的描述可以抽象为三个区域:
<img class="preview-img no-padding" align="right" src="https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*iVZpRpdN_2AAAAAAAAAAAABkARQnAQ">
<img class="preview-img no-padding" align="right" src="https://img.alicdn.com/imgextra/i2/O1CN017b7PRO1TEnquClCYx_!!6000000002351-2-tps-928-622.png">
- Header主题的标题和摘要信息内容区的导航等
- Body具体内容
@ -109,10 +125,9 @@ title: 按钮
<img class="preview-img no-padding" align="right" src="https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*KGGWQLCBfm0AAAAAAAAAAABkARQnAQ">
- Body 区部分内容被折叠或隐藏,例如单屏无法展示完整内容;
- Body 区的内容复杂度高,例如有多个分组,分组中又有独立的按钮区,这时候需要将该主题的“完成”操作从 body 区区分出来,避免混淆按钮所能影响的内容范围。
简而言之Footer 的存在就是为了要和 Body 区区分开来。
为避免页脚工具栏滥用,我们不推荐使用页脚工具栏,仅建议以下两种场景使用:
- 1对象详情页「推进」对象的进展例如审批流「通过」「驳回」。
- 2异常复杂的表单页表单的内容复杂到需要切分为多张卡片。
## 按钮顺序

View File

@ -179,7 +179,7 @@
"antd-pro-merge-less": "^3.0.11",
"antd-theme-generator": "^1.2.3",
"argos-cli": "^0.3.0",
"array-move": "^4.0.0",
"array-move": "^3.0.0",
"babel-plugin-add-react-displayname": "^0.0.5",
"bisheng": "^3.0.0",
"bisheng-plugin-description": "^0.1.4",
@ -230,7 +230,7 @@
"less-vars-to-js": "^1.3.0",
"lz-string": "^1.4.4",
"mockdate": "^3.0.0",
"node-fetch": "^2.6.0",
"node-fetch": "^3.0.0",
"open": "^8.0.1",
"prettier": "^2.3.2",
"prettier-plugin-jsdoc": "^0.3.0",