merge origin/master

This commit is contained in:
zombiej 2018-12-18 19:18:08 +08:00
commit cdf4c1e6b8
35 changed files with 595 additions and 314 deletions

View File

@ -7,25 +7,10 @@ const transformIgnorePatterns = [
module.exports = { module.exports = {
verbose: true, verbose: true,
setupFiles: [ setupFiles: ['./tests/setup.js'],
'./tests/setup.js', moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'md'],
], modulePathIgnorePatterns: ['/_site/'],
moduleFileExtensions: [ testPathIgnorePatterns: ['/node_modules/', 'dekko', 'node'],
'ts',
'tsx',
'js',
'jsx',
'json',
'md',
],
modulePathIgnorePatterns: [
'/_site/',
],
testPathIgnorePatterns: [
'/node_modules/',
'dekko',
'node',
],
transform: { transform: {
'\\.tsx?$': './node_modules/antd-tools/lib/jest/codePreprocessor', '\\.tsx?$': './node_modules/antd-tools/lib/jest/codePreprocessor',
'\\.js$': './node_modules/antd-tools/lib/jest/codePreprocessor', '\\.js$': './node_modules/antd-tools/lib/jest/codePreprocessor',
@ -41,12 +26,10 @@ module.exports = {
'!components/**/*/interface.{ts,tsx}', '!components/**/*/interface.{ts,tsx}',
], ],
transformIgnorePatterns, transformIgnorePatterns,
snapshotSerializers: [ snapshotSerializers: ['enzyme-to-json/serializer'],
'enzyme-to-json/serializer',
],
globals: { globals: {
'ts-jest': { 'ts-jest': {
tsConfigFile: './tsconfig.test.json', tsConfig: './tsconfig.test.json',
}, },
}, },
testURL: 'http://localhost', testURL: 'http://localhost',

View File

@ -1,14 +1,7 @@
// jest config for server render environment // jest config for server render environment
module.exports = { module.exports = {
setupFiles: [ setupFiles: ['./tests/setup.js'],
'./tests/setup.js', moduleFileExtensions: ['ts', 'tsx', 'js', 'md'],
],
moduleFileExtensions: [
'ts',
'tsx',
'js',
'md',
],
transform: { transform: {
'\\.tsx?$': './node_modules/antd-tools/lib/jest/codePreprocessor', '\\.tsx?$': './node_modules/antd-tools/lib/jest/codePreprocessor',
'\\.js$': './node_modules/antd-tools/lib/jest/codePreprocessor', '\\.js$': './node_modules/antd-tools/lib/jest/codePreprocessor',
@ -16,9 +9,7 @@ module.exports = {
}, },
testRegex: 'demo\\.test\\.js$', testRegex: 'demo\\.test\\.js$',
testEnvironment: 'node', testEnvironment: 'node',
snapshotSerializers: [ snapshotSerializers: ['enzyme-to-json/serializer'],
'enzyme-to-json/serializer',
],
globals: { globals: {
'ts-jest': { 'ts-jest': {
tsConfigFile: './tsconfig.test.json', tsConfigFile: './tsconfig.test.json',

View File

@ -3,22 +3,14 @@ import { mount } from 'enzyme';
import BackTop from '..'; import BackTop from '..';
describe('BackTop', () => { describe('BackTop', () => {
beforeAll(() => { it('should scroll to top after click it', async () => {
jest.useFakeTimers();
});
afterAll(() => {
jest.useRealTimers();
});
it('should scroll to top after click it', () => {
const wrapper = mount(<BackTop visibilityHeight={-1} />); const wrapper = mount(<BackTop visibilityHeight={-1} />);
document.documentElement.scrollTop = 400; document.documentElement.scrollTop = 400;
// trigger scroll manually // trigger scroll manually
wrapper.instance().handleScroll(); wrapper.instance().handleScroll();
jest.runAllTimers(); await new Promise(resolve => setTimeout(resolve, 0));
wrapper.find('.ant-back-top').simulate('click'); wrapper.find('.ant-back-top').simulate('click');
jest.runAllTimers(); await new Promise(resolve => setTimeout(resolve, 1000));
expect(Math.abs(Math.round(document.documentElement.scrollTop))).toBe(0); expect(Math.abs(Math.round(document.documentElement.scrollTop))).toBe(0);
}); });
}); });

View File

@ -73,7 +73,6 @@
font-size: @font-size-base; font-size: @font-size-base;
color: @text-color; color: @text-color;
font-weight: normal; font-weight: normal;
text-align: right;
// https://stackoverflow.com/a/22429853/3040605 // https://stackoverflow.com/a/22429853/3040605
margin-left: auto; margin-left: auto;
} }

View File

@ -4,6 +4,7 @@ import arrayTreeFilter from 'array-tree-filter';
import classNames from 'classnames'; import classNames from 'classnames';
import omit from 'omit.js'; import omit from 'omit.js';
import KeyCode from 'rc-util/lib/KeyCode'; import KeyCode from 'rc-util/lib/KeyCode';
import { polyfill } from 'react-lifecycles-compat';
import Input from '../input'; import Input from '../input';
import Icon from '../icon'; import Icon from '../icon';
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider'; import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
@ -103,6 +104,7 @@ export interface CascaderState {
value: string[]; value: string[];
popupVisible: boolean | undefined; popupVisible: boolean | undefined;
flattenOptions: CascaderOptionType[][] | undefined; flattenOptions: CascaderOptionType[][] | undefined;
prevProps: CascaderProps;
} }
interface CascaderLocale { interface CascaderLocale {
@ -180,9 +182,29 @@ function getFilledFieldNames(props: CascaderProps) {
return names; return names;
} }
function flattenTree(
options: CascaderOptionType[],
props: CascaderProps,
ancestor: CascaderOptionType[] = [],
) {
const names: FilledFieldNamesType = getFilledFieldNames(props);
let flattenOptions = [] as CascaderOptionType[][];
const childrenName = names.children;
options.forEach(option => {
const path = ancestor.concat(option);
if (props.changeOnSelect || !option[childrenName] || !option[childrenName].length) {
flattenOptions.push(path);
}
if (option[childrenName]) {
flattenOptions = flattenOptions.concat(flattenTree(option[childrenName], props, path));
}
});
return flattenOptions;
}
const defaultDisplayRender = (label: string[]) => label.join(' / '); const defaultDisplayRender = (label: string[]) => label.join(' / ');
export default class Cascader extends React.Component<CascaderProps, CascaderState> { class Cascader extends React.Component<CascaderProps, CascaderState> {
static defaultProps = { static defaultProps = {
placeholder: 'Please select', placeholder: 'Please select',
transitionName: 'slide-up', transitionName: 'slide-up',
@ -193,6 +215,24 @@ export default class Cascader extends React.Component<CascaderProps, CascaderSta
notFoundContent: 'Not Found', notFoundContent: 'Not Found',
}; };
static getDerivedStateFromProps(nextProps: CascaderProps, { prevProps }: CascaderState) {
const newState: Partial<CascaderState> = {
prevProps: nextProps,
};
if ('value' in nextProps) {
newState.value = nextProps.value || [];
}
if ('popupVisible' in nextProps) {
newState.popupVisible = nextProps.popupVisible;
}
if (nextProps.showSearch && prevProps.options !== nextProps.options) {
newState.flattenOptions = flattenTree(nextProps.options, nextProps);
}
return newState;
}
cachedOptions: CascaderOptionType[]; cachedOptions: CascaderOptionType[];
private input: Input; private input: Input;
@ -204,24 +244,11 @@ export default class Cascader extends React.Component<CascaderProps, CascaderSta
inputValue: '', inputValue: '',
inputFocused: false, inputFocused: false,
popupVisible: props.popupVisible, popupVisible: props.popupVisible,
flattenOptions: props.showSearch ? this.flattenTree(props.options, props) : undefined, flattenOptions: props.showSearch ? flattenTree(props.options, props) : undefined,
prevProps: props,
}; };
} }
componentWillReceiveProps(nextProps: CascaderProps) {
if ('value' in nextProps) {
this.setState({ value: nextProps.value || [] });
}
if ('popupVisible' in nextProps) {
this.setState({ popupVisible: nextProps.popupVisible });
}
if (nextProps.showSearch && this.props.options !== nextProps.options) {
this.setState({
flattenOptions: this.flattenTree(nextProps.options, nextProps),
});
}
}
handleChange = (value: any, selectedOptions: CascaderOptionType[]) => { handleChange = (value: any, selectedOptions: CascaderOptionType[]) => {
this.setState({ inputValue: '' }); this.setState({ inputValue: '' });
if (selectedOptions[0].__IS_FILTERED_OPTION) { if (selectedOptions[0].__IS_FILTERED_OPTION) {
@ -311,26 +338,6 @@ export default class Cascader extends React.Component<CascaderProps, CascaderSta
} }
}; };
flattenTree(
options: CascaderOptionType[],
props: CascaderProps,
ancestor: CascaderOptionType[] = [],
) {
const names: FilledFieldNamesType = getFilledFieldNames(props);
let flattenOptions = [] as CascaderOptionType[][];
const childrenName = names.children;
options.forEach(option => {
const path = ancestor.concat(option);
if (props.changeOnSelect || !option[childrenName] || !option[childrenName].length) {
flattenOptions.push(path);
}
if (option[childrenName]) {
flattenOptions = flattenOptions.concat(this.flattenTree(option[childrenName], props, path));
}
});
return flattenOptions;
}
generateFilteredOptions(prefixCls: string | undefined) { generateFilteredOptions(prefixCls: string | undefined) {
const { showSearch, notFoundContent } = this.props; const { showSearch, notFoundContent } = this.props;
const names: FilledFieldNamesType = getFilledFieldNames(this.props); const names: FilledFieldNamesType = getFilledFieldNames(this.props);
@ -567,3 +574,7 @@ export default class Cascader extends React.Component<CascaderProps, CascaderSta
); );
} }
} }
polyfill(Cascader);
export default Cascader;

View File

@ -6,7 +6,7 @@ export interface CommentProps {
/** List of action items rendered below the comment content */ /** List of action items rendered below the comment content */
actions?: Array<React.ReactNode>; actions?: Array<React.ReactNode>;
/** The element to display as the comment author. */ /** The element to display as the comment author. */
author?: string; author?: React.ReactNode;
/** The element to display as the comment avatar - generally an antd Avatar */ /** The element to display as the comment avatar - generally an antd Avatar */
avatar?: React.ReactNode; avatar?: React.ReactNode;
/** className of comment */ /** className of comment */

View File

@ -181,24 +181,22 @@ class WeekPicker extends React.Component<any, WeekPickerState> {
<span className={`${prefixCls}-picker-icon`}>{suffixIcon}</span> <span className={`${prefixCls}-picker-icon`}>{suffixIcon}</span>
))) || <Icon type="calendar" className={`${prefixCls}-picker-icon`} />; ))) || <Icon type="calendar" className={`${prefixCls}-picker-icon`} />;
const input = ({ value }: { value: moment.Moment | undefined }) => { const input = ({ value }: { value: moment.Moment | undefined }) => (
return ( <span style={{ display: 'inline-block', width: '100%' }}>
<span style={{ display: 'inline-block' }}> <input
<input ref={this.saveInput}
ref={this.saveInput} disabled={disabled}
disabled={disabled} readOnly
readOnly value={(value && value.format(format)) || ''}
value={(value && value.format(format)) || ''} placeholder={placeholder}
placeholder={placeholder} className={pickerInputClass}
className={pickerInputClass} onFocus={onFocus}
onFocus={onFocus} onBlur={onBlur}
onBlur={onBlur} />
/> {clearIcon}
{clearIcon} {inputIcon}
{inputIcon} </span>
</span> );
);
};
return ( return (
<span className={classNames(className, pickerClass)} style={style} id={id}> <span className={classNames(className, pickerClass)} style={style} id={id}>
<RcDatePicker <RcDatePicker

View File

@ -6,7 +6,7 @@ exports[`WeekPicker should support style prop 1`] = `
style="width: 400px;" style="width: 400px;"
> >
<span <span
style="display: inline-block;" style="display: inline-block; width: 100%;"
> >
<input <input
class="ant-calendar-picker-input ant-input" class="ant-calendar-picker-input ant-input"

View File

@ -112,7 +112,7 @@ exports[`renders ./components/date-picker/demo/basic.md correctly 1`] = `
class="ant-calendar-picker" class="ant-calendar-picker"
> >
<span <span
style="display:inline-block" style="display:inline-block;width:100%"
> >
<input <input
class="ant-calendar-picker-input ant-input" class="ant-calendar-picker-input ant-input"
@ -1146,7 +1146,7 @@ exports[`renders ./components/date-picker/demo/size.md correctly 1`] = `
class="ant-calendar-picker ant-calendar-picker-default" class="ant-calendar-picker ant-calendar-picker-default"
> >
<span <span
style="display:inline-block" style="display:inline-block;width:100%"
> >
<input <input
class="ant-calendar-picker-input ant-input" class="ant-calendar-picker-input ant-input"
@ -1353,7 +1353,7 @@ exports[`renders ./components/date-picker/demo/suffix.md correctly 1`] = `
class="ant-calendar-picker" class="ant-calendar-picker"
> >
<span <span
style="display:inline-block" style="display:inline-block;width:100%"
> >
<input <input
class="ant-calendar-picker-input ant-input" class="ant-calendar-picker-input ant-input"
@ -1455,7 +1455,7 @@ exports[`renders ./components/date-picker/demo/suffix.md correctly 1`] = `
class="ant-calendar-picker" class="ant-calendar-picker"
> >
<span <span
style="display:inline-block" style="display:inline-block;width:100%"
> >
<input <input
class="ant-calendar-picker-input ant-input" class="ant-calendar-picker-input ant-input"

View File

@ -1,5 +1,6 @@
import React from 'react'; import React from 'react';
import { mount } from 'enzyme'; import { mount } from 'enzyme';
import moment from 'moment';
import DatePicker from '..'; import DatePicker from '..';
const { RangePicker } = DatePicker; const { RangePicker } = DatePicker;
@ -38,6 +39,7 @@ describe('DatePicker with showTime', () => {
onChange={onChangeFn} onChange={onChangeFn}
onOk={onOkFn} onOk={onOkFn}
onOpenChange={onOpenChangeFn} onOpenChange={onOpenChangeFn}
defaultValue={moment()}
/>, />,
); );

View File

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

View File

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

View File

@ -67,7 +67,7 @@ export interface RangePickerProps extends PickerProps {
defaultPickerValue?: RangePickerValue; defaultPickerValue?: RangePickerValue;
onChange?: (dates: RangePickerValue, dateStrings: [string, string]) => void; onChange?: (dates: RangePickerValue, dateStrings: [string, string]) => void;
onCalendarChange?: (dates: RangePickerValue, dateStrings: [string, string]) => void; onCalendarChange?: (dates: RangePickerValue, dateStrings: [string, string]) => void;
onOk?: (selectedTime: moment.Moment) => void; onOk?: (selectedTime: moment.Moment[]) => void;
showTime?: TimePickerProps | boolean; showTime?: TimePickerProps | boolean;
ranges?: { ranges?: {
[range: string]: RangePickerPresetRange; [range: string]: RangePickerPresetRange;

View File

@ -3118,6 +3118,163 @@ exports[`renders ./components/form/demo/validate-other.md correctly 1`] = `
</div> </div>
</div> </div>
</div> </div>
<div
class="ant-row ant-form-item"
>
<div
class="ant-col-6 ant-form-item-label"
>
<label
class=""
for="checkbox-group"
title="Checkbox.Group"
>
Checkbox.Group
</label>
</div>
<div
class="ant-col-14 ant-form-item-control-wrapper"
>
<div
class="ant-form-item-control has-success"
>
<span
class="ant-form-item-children"
>
<div
class="ant-checkbox-group"
data-__field="[object Object]"
data-__meta="[object Object]"
id="checkbox-group"
style="width:100%"
>
<div
class="ant-row"
>
<div
class="ant-col-8"
>
<label
class="ant-checkbox-wrapper ant-checkbox-wrapper-checked"
>
<span
class="ant-checkbox ant-checkbox-checked"
>
<input
checked=""
class="ant-checkbox-input"
type="checkbox"
value="A"
/>
<span
class="ant-checkbox-inner"
/>
</span>
<span>
A
</span>
</label>
</div>
<div
class="ant-col-8"
>
<label
class="ant-checkbox-wrapper ant-checkbox-wrapper-checked ant-checkbox-wrapper-disabled"
>
<span
class="ant-checkbox ant-checkbox-checked ant-checkbox-disabled"
>
<input
checked=""
class="ant-checkbox-input"
disabled=""
type="checkbox"
value="B"
/>
<span
class="ant-checkbox-inner"
/>
</span>
<span>
B
</span>
</label>
</div>
<div
class="ant-col-8"
>
<label
class="ant-checkbox-wrapper"
>
<span
class="ant-checkbox"
>
<input
class="ant-checkbox-input"
type="checkbox"
value="C"
/>
<span
class="ant-checkbox-inner"
/>
</span>
<span>
C
</span>
</label>
</div>
<div
class="ant-col-8"
>
<label
class="ant-checkbox-wrapper"
>
<span
class="ant-checkbox"
>
<input
class="ant-checkbox-input"
type="checkbox"
value="D"
/>
<span
class="ant-checkbox-inner"
/>
</span>
<span>
D
</span>
</label>
</div>
<div
class="ant-col-8"
>
<label
class="ant-checkbox-wrapper"
>
<span
class="ant-checkbox"
>
<input
class="ant-checkbox-input"
type="checkbox"
value="E"
/>
<span
class="ant-checkbox-inner"
/>
</span>
<span>
E
</span>
</label>
</div>
</div>
</div>
</span>
</div>
</div>
</div>
<div <div
class="ant-row ant-form-item" class="ant-row ant-form-item"
> >

View File

@ -16,7 +16,8 @@ Demostration for validataion configuration for form controls which are not show
````jsx ````jsx
import { import {
Form, Select, InputNumber, Switch, Radio, Form, Select, InputNumber, Switch, Radio,
Slider, Button, Upload, Icon, Rate, Slider, Button, Upload, Icon, Rate, Checkbox,
Row, Col,
} from 'antd'; } from 'antd';
const FormItem = Form.Item; const FormItem = Form.Item;
@ -147,6 +148,25 @@ class Demo extends React.Component {
)} )}
</FormItem> </FormItem>
<Form.Item
{...formItemLayout}
label="Checkbox.Group"
>
{getFieldDecorator("checkbox-group", {
initialValue: ["A", "B"],
})(
<Checkbox.Group style={{ width: "100%" }}>
<Row>
<Col span={8}><Checkbox value="A">A</Checkbox></Col>
<Col span={8}><Checkbox disabled value="B">B</Checkbox></Col>
<Col span={8}><Checkbox value="C">C</Checkbox></Col>
<Col span={8}><Checkbox value="D">D</Checkbox></Col>
<Col span={8}><Checkbox value="E">E</Checkbox></Col>
</Row>
</Checkbox.Group>
)}
</Form.Item>
<FormItem <FormItem
{...formItemLayout} {...formItemLayout}
label="Rate" label="Rate"

View File

@ -455,6 +455,9 @@ form {
.@{ant-prefix}-select { .@{ant-prefix}-select {
&-selection { &-selection {
border-color: @warning-color; border-color: @warning-color;
&:hover {
border-color: @warning-color;
}
} }
&-open .@{ant-prefix}-select-selection, &-open .@{ant-prefix}-select-selection,
&-focused .@{ant-prefix}-select-selection { &-focused .@{ant-prefix}-select-selection {
@ -501,6 +504,9 @@ form {
.@{ant-prefix}-select { .@{ant-prefix}-select {
&-selection { &-selection {
border-color: @error-color; border-color: @error-color;
&:hover {
border-color: @error-color;
}
} }
&-open .@{ant-prefix}-select-selection, &-open .@{ant-prefix}-select-selection,
&-focused .@{ant-prefix}-select-selection { &-focused .@{ant-prefix}-select-selection {

View File

@ -25,6 +25,24 @@
.active(@border-color); .active(@border-color);
} }
// Input prefix
.@{ant-prefix}-input-affix-wrapper {
.@{ant-prefix}-input {
&,
&:hover {
border-color: @border-color;
}
&:focus {
.active(@border-color);
}
}
&:hover .@{ant-prefix}-input:not(.@{ant-prefix}-input-disabled) {
border-color: @border-color;
}
}
.@{ant-prefix}-input-prefix { .@{ant-prefix}-input-prefix {
color: @text-color; color: @text-color;
} }

View File

@ -267,28 +267,26 @@
&-wrap, &-wrap,
> .@{inputClass} { > .@{inputClass} {
&:not(:first-child):not(:last-child) { &:not(:first-child):not(:last-child) {
border-right-width: 1px; border-right-width: @border-width-base;
border-right-color: transparent;
&:hover { &:hover {
.hover(); z-index: 1;
} }
&:focus { &:focus {
.active(); z-index: 1;
} }
} }
} }
& > * { & > * {
border-radius: 0; border-radius: 0;
border-right-width: 0;
vertical-align: top; // https://github.com/ant-design/ant-design-pro/issues/139 vertical-align: top; // https://github.com/ant-design/ant-design-pro/issues/139
float: none; float: none;
display: inline-block; display: inline-block;
} }
// https://github.com/ant-design/ant-design/issues/11863 & > *:not(:last-child) {
& > span:not(:last-child) > .@{inputClass} { border-right-width: @border-width-base;
border-right-width: 0; margin-right: -@border-width-base;
} }
// Undo float for .ant-input-group .ant-input // Undo float for .ant-input-group .ant-input
@ -305,12 +303,11 @@
& > .@{ant-prefix}-time-picker .@{ant-prefix}-time-picker-input { & > .@{ant-prefix}-time-picker .@{ant-prefix}-time-picker-input {
border-radius: 0; border-radius: 0;
border-right-width: @border-width-base; border-right-width: @border-width-base;
border-right-color: transparent;
&:hover { &:hover {
.hover(); z-index: 1;
} }
&:focus { &:focus {
.active(); z-index: 1;
} }
} }
@ -336,16 +333,6 @@
border-top-right-radius: @border-radius-base; border-top-right-radius: @border-radius-base;
border-bottom-right-radius: @border-radius-base; border-bottom-right-radius: @border-radius-base;
border-right-width: @border-width-base; border-right-width: @border-width-base;
border-right-color: @input-border-color;
&:hover {
.hover();
}
&:focus {
.active();
.@{ant-prefix}-cascader-input {
.active();
}
}
} }
// https://github.com/ant-design/ant-design/issues/12493 // https://github.com/ant-design/ant-design/issues/12493
@ -365,13 +352,14 @@
} }
.@{inputClass} { .@{inputClass} {
position: static; position: relative;
} }
.@{inputClass}-prefix, .@{inputClass}-prefix,
.@{inputClass}-suffix { .@{inputClass}-suffix {
position: absolute; position: absolute;
top: 50%; top: 50%;
z-index: 2;
transform: translateY(-50%); transform: translateY(-50%);
line-height: 0; line-height: 0;
color: @input-color; color: @input-color;

View File

@ -2,6 +2,7 @@ import * as React from 'react';
import * as PropTypes from 'prop-types'; import * as PropTypes from 'prop-types';
import classNames from 'classnames'; import classNames from 'classnames';
import shallowEqual from 'shallowequal'; import shallowEqual from 'shallowequal';
import { polyfill } from 'react-lifecycles-compat';
import Radio from './radio'; import Radio from './radio';
import { import {
RadioGroupProps, RadioGroupProps,
@ -23,7 +24,7 @@ function getCheckedValue(children: React.ReactNode) {
return matched ? { value } : undefined; return matched ? { value } : undefined;
} }
export default class RadioGroup extends React.Component<RadioGroupProps, RadioGroupState> { class RadioGroup extends React.Component<RadioGroupProps, RadioGroupState> {
static defaultProps = { static defaultProps = {
disabled: false, disabled: false,
buttonStyle: 'outline' as RadioGroupButtonStyle, buttonStyle: 'outline' as RadioGroupButtonStyle,
@ -33,6 +34,22 @@ export default class RadioGroup extends React.Component<RadioGroupProps, RadioGr
radioGroup: PropTypes.any, radioGroup: PropTypes.any,
}; };
static getDerivedStateFromProps(nextProps: RadioGroupProps) {
if ('value' in nextProps) {
return {
value: nextProps.value,
};
} else {
const checkedValue = getCheckedValue(nextProps.children);
if (checkedValue) {
return {
value: checkedValue.value,
};
}
}
return null;
}
constructor(props: RadioGroupProps) { constructor(props: RadioGroupProps) {
super(props); super(props);
let value; let value;
@ -60,21 +77,6 @@ export default class RadioGroup extends React.Component<RadioGroupProps, RadioGr
}; };
} }
componentWillReceiveProps(nextProps: RadioGroupProps) {
if ('value' in nextProps) {
this.setState({
value: nextProps.value,
});
} else {
const checkedValue = getCheckedValue(nextProps.children);
if (checkedValue) {
this.setState({
value: checkedValue.value,
});
}
}
}
shouldComponentUpdate(nextProps: RadioGroupProps, nextState: RadioGroupState) { shouldComponentUpdate(nextProps: RadioGroupProps, nextState: RadioGroupState) {
return !shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState); return !shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState);
} }
@ -162,3 +164,6 @@ export default class RadioGroup extends React.Component<RadioGroupProps, RadioGr
return <ConfigConsumer>{this.renderGroup}</ConfigConsumer>; return <ConfigConsumer>{this.renderGroup}</ConfigConsumer>;
} }
} }
polyfill(RadioGroup);
export default RadioGroup;

View File

@ -116,6 +116,7 @@
.@{radio-inner-prefix-cls} { .@{radio-inner-prefix-cls} {
border-color: @border-color-base !important; border-color: @border-color-base !important;
background-color: @input-disabled-bg; background-color: @input-disabled-bg;
cursor: not-allowed;
&:after { &:after {
background-color: fade(@black, 20%); background-color: fade(@black, 20%);
} }

View File

@ -14,7 +14,7 @@ describe('Spin', () => {
.find('.ant-spin-nested-loading') .find('.ant-spin-nested-loading')
.at(0) .at(0)
.prop('style'), .prop('style'),
).toBe(null); ).toBeFalsy();
expect( expect(
wrapper wrapper
.find('.ant-spin') .find('.ant-spin')

View File

@ -1,7 +1,6 @@
import * as React from 'react'; import * as React from 'react';
import * as PropTypes from 'prop-types'; import * as PropTypes from 'prop-types';
import classNames from 'classnames'; import classNames from 'classnames';
import Animate from 'rc-animate';
import omit from 'omit.js'; import omit from 'omit.js';
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider'; import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
@ -93,13 +92,6 @@ class Spin extends React.Component<SpinProps, SpinState> {
return !!(this.props && this.props.children); return !!(this.props && this.props.children);
} }
componentDidMount() {
const { spinning, delay } = this.props;
if (shouldDelay(spinning, delay)) {
this.delayTimeout = window.setTimeout(this.delayUpdateSpinning, delay);
}
}
componentWillUnmount() { componentWillUnmount() {
if (this.debounceTimeout) { if (this.debounceTimeout) {
clearTimeout(this.debounceTimeout); clearTimeout(this.debounceTimeout);
@ -151,6 +143,7 @@ class Spin extends React.Component<SpinProps, SpinState> {
size, size,
tip, tip,
wrapperClassName, wrapperClassName,
style,
...restProps ...restProps
} = this.props; } = this.props;
const { spinning } = this.state; const { spinning } = this.state;
@ -171,33 +164,22 @@ class Spin extends React.Component<SpinProps, SpinState> {
const divProps = omit(restProps, ['spinning', 'delay', 'indicator']); const divProps = omit(restProps, ['spinning', 'delay', 'indicator']);
const spinElement = ( const spinElement = (
<div {...divProps} className={spinClassName}> <div {...divProps} style={style} className={spinClassName}>
{renderIndicator(prefixCls, this.props)} {renderIndicator(prefixCls, this.props)}
{tip ? <div className={`${prefixCls}-text`}>{tip}</div> : null} {tip ? <div className={`${prefixCls}-text`}>{tip}</div> : null}
</div> </div>
); );
if (this.isNestedPattern()) { if (this.isNestedPattern()) {
let animateClassName = prefixCls + '-nested-loading'; const containerClassName = classNames(`${prefixCls}-container`, {
if (wrapperClassName) {
animateClassName += ' ' + wrapperClassName;
}
const containerClassName = classNames({
[`${prefixCls}-container`]: true,
[`${prefixCls}-blur`]: spinning, [`${prefixCls}-blur`]: spinning,
}); });
return ( return (
<Animate <div {...divProps} className={classNames(`${prefixCls}-nested-loading`, wrapperClassName)}>
{...divProps}
component="div"
className={animateClassName}
style={null}
transitionName="fade"
>
{spinning && <div key="loading">{spinElement}</div>} {spinning && <div key="loading">{spinElement}</div>}
<div className={containerClassName} key="container"> <div className={containerClassName} key="container">
{this.props.children} {this.props.children}
</div> </div>
</Animate> </div>
); );
} }
return spinElement; return spinElement;

View File

@ -26,7 +26,7 @@
display: block; display: block;
position: absolute; position: absolute;
height: 100%; height: 100%;
max-height: 360px; max-height: 400px;
width: 100%; width: 100%;
z-index: 4; z-index: 4;
.@{spin-prefix-cls}-dot { .@{spin-prefix-cls}-dot {
@ -75,19 +75,6 @@
&-container { &-container {
position: relative; position: relative;
transition: opacity 0.3s; transition: opacity 0.3s;
.clearfix;
}
&-blur {
pointer-events: none;
user-select: none;
overflow: hidden;
opacity: 0.5;
-webkit-filter: blur(0.5px);
filter: blur(0.5px);
/* autoprefixer: off */
filter: ~'progid\:DXImageTransform\.Microsoft\.Blur(PixelRadius\=1, MakeShadow\=false)';
&:after { &:after {
content: ''; content: '';
@ -97,12 +84,27 @@
top: 0; top: 0;
bottom: 0; bottom: 0;
background: @component-background; background: @component-background;
opacity: 0.3; opacity: 0;
pointer-events: none;
transition: all 0.3s; transition: all 0.3s;
height: 100%;
width: 100%;
z-index: 10; z-index: 10;
} }
} }
&-blur {
pointer-events: none;
user-select: none;
overflow: hidden;
opacity: 0.5;
&:after {
opacity: 0.4;
pointer-events: auto;
}
}
// tip // tip
// ------------------------------ // ------------------------------
&-tip { &-tip {

View File

@ -58,8 +58,13 @@ export default class SelectionCheckboxAll<T> extends React.Component<
}); });
} }
checkSelection(data: T[], type: string, byDefaultChecked: boolean) { checkSelection(
const { store, getCheckboxPropsByItem, getRecordKey } = this.props; props: SelectionCheckboxAllProps<T>,
data: T[],
type: string,
byDefaultChecked: boolean,
) {
const { store, getCheckboxPropsByItem, getRecordKey } = props || this.props;
// type should be 'every' | 'some' // type should be 'every' | 'some'
if (type === 'every' || type === 'some') { if (type === 'every' || type === 'some') {
return byDefaultChecked return byDefaultChecked
@ -93,8 +98,9 @@ export default class SelectionCheckboxAll<T> extends React.Component<
checked = false; checked = false;
} else { } else {
checked = store.getState().selectionDirty checked = store.getState().selectionDirty
? this.checkSelection(data, 'every', false) ? this.checkSelection(props, data, 'every', false)
: this.checkSelection(data, 'every', false) || this.checkSelection(data, 'every', true); : this.checkSelection(props, data, 'every', false) ||
this.checkSelection(props, data, 'every', true);
} }
return checked; return checked;
} }
@ -106,15 +112,17 @@ export default class SelectionCheckboxAll<T> extends React.Component<
indeterminate = false; indeterminate = false;
} else { } else {
indeterminate = store.getState().selectionDirty indeterminate = store.getState().selectionDirty
? this.checkSelection(data, 'some', false) && !this.checkSelection(data, 'every', false) ? this.checkSelection(props, data, 'some', false) &&
: (this.checkSelection(data, 'some', false) && !this.checkSelection(props, data, 'every', false)
!this.checkSelection(data, 'every', false)) || : (this.checkSelection(props, data, 'some', false) &&
(this.checkSelection(data, 'some', true) && !this.checkSelection(data, 'every', true)); !this.checkSelection(props, data, 'every', false)) ||
(this.checkSelection(props, data, 'some', true) &&
!this.checkSelection(props, data, 'every', true));
} }
return indeterminate; return indeterminate;
} }
handleSelectAllChagne = (e: CheckboxChangeEvent) => { handleSelectAllChange = (e: CheckboxChangeEvent) => {
const checked = e.target.checked; const checked = e.target.checked;
this.props.onSelect(checked ? 'all' : 'removeAll', 0, null); this.props.onSelect(checked ? 'all' : 'removeAll', 0, null);
}; };
@ -171,7 +179,7 @@ export default class SelectionCheckboxAll<T> extends React.Component<
checked={checked} checked={checked}
indeterminate={indeterminate} indeterminate={indeterminate}
disabled={disabled} disabled={disabled}
onChange={this.handleSelectAllChagne} onChange={this.handleSelectAllChange}
/> />
{customSelections} {customSelections}
</div> </div>

View File

@ -25,6 +25,7 @@ import {
TableComponents, TableComponents,
RowSelectionType, RowSelectionType,
TableLocale, TableLocale,
AdditionalCellProps,
ColumnProps, ColumnProps,
CompareFn, CompareFn,
TableStateFilters, TableStateFilters,
@ -814,6 +815,7 @@ export default class Table<T> extends React.Component<TableProps<T>, TableState<
const key = this.getColumnKey(column, i) as string; const key = this.getColumnKey(column, i) as string;
let filterDropdown; let filterDropdown;
let sortButton; let sortButton;
let onHeaderCell = column.onHeaderCell;
const sortTitle = this.getColumnTitle(column.title, {}) || locale.sortTitle; const sortTitle = this.getColumnTitle(column.title, {}) || locale.sortTitle;
const isSortColumn = this.isSortColumn(column); const isSortColumn = this.isSortColumn(column);
if ((column.filters && column.filters.length > 0) || column.filterDropdown) { if ((column.filters && column.filters.length > 0) || column.filterDropdown) {
@ -848,8 +850,27 @@ export default class Table<T> extends React.Component<TableProps<T>, TableState<
/> />
</div> </div>
); );
onHeaderCell = (col: Column<T>) => {
let colProps: AdditionalCellProps = {};
// Get original first
if (column.onHeaderCell) {
colProps = {
...column.onHeaderCell(col),
};
}
// Add sorter logic
const onHeaderCellClick = colProps.onClick;
colProps.onClick = (...args) => {
this.toggleSortOrder(column);
if (onHeaderCellClick) {
onHeaderCellClick(...args);
}
};
return colProps;
};
} }
const sortTitleString = (sortButton && typeof sortTitle === 'string') ? sortTitle : undefined; const sortTitleString = sortButton && typeof sortTitle === 'string' ? sortTitle : undefined;
return { return {
...column, ...column,
className: classNames(column.className, { className: classNames(column.className, {
@ -863,13 +884,13 @@ export default class Table<T> extends React.Component<TableProps<T>, TableState<
key="title" key="title"
title={sortTitleString} title={sortTitleString}
className={sortButton ? `${prefixCls}-column-sorters` : undefined} className={sortButton ? `${prefixCls}-column-sorters` : undefined}
onClick={() => this.toggleSortOrder(column)}
> >
{this.renderColumnTitle(column.title)} {this.renderColumnTitle(column.title)}
{sortButton} {sortButton}
</div>, </div>,
filterDropdown, filterDropdown,
], ],
onHeaderCell,
}; };
}); });
} }

View File

@ -1038,8 +1038,12 @@ exports[`renders ./components/table/demo/custom-filter-panel.md correctly 1`] =
class="" class=""
> >
<colgroup> <colgroup>
<col /> <col
<col /> style="width:30%;min-width:30%"
/>
<col
style="width:20%;min-width:20%"
/>
<col /> <col />
</colgroup> </colgroup>
<thead <thead
@ -1053,31 +1057,48 @@ exports[`renders ./components/table/demo/custom-filter-panel.md correctly 1`] =
Name Name
</div> </div>
<i <i
class="anticon anticon-smile-o ant-table-filter-icon ant-dropdown-trigger" class="anticon anticon-search ant-table-filter-icon ant-dropdown-trigger"
style="color:#aaa"
title="Filter menu" title="Filter menu"
> >
<svg <svg
aria-hidden="true" aria-hidden="true"
class="" class=""
data-icon="smile" data-icon="search"
fill="currentColor" fill="currentColor"
height="1em" height="1em"
viewBox="64 64 896 896" viewBox="64 64 896 896"
width="1em" width="1em"
> >
<path <path
d="M288 421a48 48 0 1 0 96 0 48 48 0 1 0-96 0zm352 0a48 48 0 1 0 96 0 48 48 0 1 0-96 0zM512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm263 711c-34.2 34.2-74 61-118.3 79.8C611 874.2 562.3 884 512 884c-50.3 0-99-9.8-144.8-29.2A370.4 370.4 0 0 1 248.9 775c-34.2-34.2-61-74-79.8-118.3C149.8 611 140 562.3 140 512s9.8-99 29.2-144.8A370.4 370.4 0 0 1 249 248.9c34.2-34.2 74-61 118.3-79.8C413 149.8 461.7 140 512 140c50.3 0 99 9.8 144.8 29.2A370.4 370.4 0 0 1 775.1 249c34.2 34.2 61 74 79.8 118.3C874.2 413 884 461.7 884 512s-9.8 99-29.2 144.8A368.89 368.89 0 0 1 775 775zM664 533h-48.1c-4.2 0-7.8 3.2-8.1 7.4C604 589.9 562.5 629 512 629s-92.1-39.1-95.8-88.6c-.3-4.2-3.9-7.4-8.1-7.4H360a8 8 0 0 0-8 8.4c4.4 84.3 74.5 151.6 160 151.6s155.6-67.3 160-151.6a8 8 0 0 0-8-8.4z" d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0 0 11.6 0l43.6-43.5a8.2 8.2 0 0 0 0-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
/> />
</svg> </svg>
</i> </i>
</th> </th>
<th <th
class="" class="ant-table-column-has-actions ant-table-column-has-filters"
> >
<div> <div>
Age Age
</div> </div>
<i
class="anticon anticon-search ant-table-filter-icon ant-dropdown-trigger"
title="Filter menu"
>
<svg
aria-hidden="true"
class=""
data-icon="search"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0 0 11.6 0l43.6-43.5a8.2 8.2 0 0 0 0-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
/>
</svg>
</i>
</th> </th>
<th <th
class="ant-table-column-has-actions ant-table-column-has-filters" class="ant-table-column-has-actions ant-table-column-has-filters"
@ -1086,20 +1107,20 @@ exports[`renders ./components/table/demo/custom-filter-panel.md correctly 1`] =
Address Address
</div> </div>
<i <i
class="anticon anticon-filter ant-dropdown-trigger" class="anticon anticon-search ant-table-filter-icon ant-dropdown-trigger"
title="Filter menu" title="Filter menu"
> >
<svg <svg
aria-hidden="true" aria-hidden="true"
class="" class=""
data-icon="filter" data-icon="search"
fill="currentColor" fill="currentColor"
height="1em" height="1em"
viewBox="64 64 896 896" viewBox="64 64 896 896"
width="1em" width="1em"
> >
<path <path
d="M349 838c0 17.7 14.2 32 31.8 32h262.4c17.6 0 31.8-14.3 31.8-32V642H349v196zm531.1-684H143.9c-24.5 0-39.8 26.7-27.5 48l221.3 376h348.8l221.3-376c12.1-21.3-3.2-48-27.7-48z" d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0 0 11.6 0l43.6-43.5a8.2 8.2 0 0 0 0-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
/> />
</svg> </svg>
</i> </i>
@ -1120,17 +1141,35 @@ exports[`renders ./components/table/demo/custom-filter-panel.md correctly 1`] =
class="ant-table-row-indent indent-level-0" class="ant-table-row-indent indent-level-0"
style="padding-left:0px" style="padding-left:0px"
/> />
John Brown <span>
</td> <span
<td class=""
class="" >
> John Brown
32 </span>
</span>
</td> </td>
<td <td
class="ant-table-column-has-actions ant-table-column-has-filters" class="ant-table-column-has-actions ant-table-column-has-filters"
> >
New York No. 1 Lake Park <span>
<span
class=""
>
32
</span>
</span>
</td>
<td
class="ant-table-column-has-actions ant-table-column-has-filters"
>
<span>
<span
class=""
>
New York No. 1 Lake Park
</span>
</span>
</td> </td>
</tr> </tr>
<tr <tr
@ -1144,17 +1183,35 @@ exports[`renders ./components/table/demo/custom-filter-panel.md correctly 1`] =
class="ant-table-row-indent indent-level-0" class="ant-table-row-indent indent-level-0"
style="padding-left:0px" style="padding-left:0px"
/> />
Joe Black <span>
</td> <span
<td class=""
class="" >
> Joe Black
42 </span>
</span>
</td> </td>
<td <td
class="ant-table-column-has-actions ant-table-column-has-filters" class="ant-table-column-has-actions ant-table-column-has-filters"
> >
London No. 1 Lake Park <span>
<span
class=""
>
42
</span>
</span>
</td>
<td
class="ant-table-column-has-actions ant-table-column-has-filters"
>
<span>
<span
class=""
>
London No. 1 Lake Park
</span>
</span>
</td> </td>
</tr> </tr>
<tr <tr
@ -1168,17 +1225,35 @@ exports[`renders ./components/table/demo/custom-filter-panel.md correctly 1`] =
class="ant-table-row-indent indent-level-0" class="ant-table-row-indent indent-level-0"
style="padding-left:0px" style="padding-left:0px"
/> />
Jim Green <span>
</td> <span
<td class=""
class="" >
> Jim Green
32 </span>
</span>
</td> </td>
<td <td
class="ant-table-column-has-actions ant-table-column-has-filters" class="ant-table-column-has-actions ant-table-column-has-filters"
> >
Sidney No. 1 Lake Park <span>
<span
class=""
>
32
</span>
</span>
</td>
<td
class="ant-table-column-has-actions ant-table-column-has-filters"
>
<span>
<span
class=""
>
Sidney No. 1 Lake Park
</span>
</span>
</td> </td>
</tr> </tr>
<tr <tr
@ -1192,17 +1267,35 @@ exports[`renders ./components/table/demo/custom-filter-panel.md correctly 1`] =
class="ant-table-row-indent indent-level-0" class="ant-table-row-indent indent-level-0"
style="padding-left:0px" style="padding-left:0px"
/> />
Jim Red <span>
</td> <span
<td class=""
class="" >
> Jim Red
32 </span>
</span>
</td> </td>
<td <td
class="ant-table-column-has-actions ant-table-column-has-filters" class="ant-table-column-has-actions ant-table-column-has-filters"
> >
London No. 2 Lake Park <span>
<span
class=""
>
32
</span>
</span>
</td>
<td
class="ant-table-column-has-actions ant-table-column-has-filters"
>
<span>
<span
class=""
>
London No. 2 Lake Park
</span>
</span>
</td> </td>
</tr> </tr>
</tbody> </tbody>

View File

@ -17,6 +17,7 @@ Implement a customized column search example via `filterDropdown`.
import { import {
Table, Input, Button, Icon, Table, Input, Button, Icon,
} from 'antd'; } from 'antd';
import Highlighter from 'react-highlight-words';
const data = [{ const data = [{
key: '1', key: '1',
@ -45,12 +46,60 @@ class App extends React.Component {
searchText: '', searchText: '',
}; };
handleSearch = (selectedKeys, confirm) => () => { getColumnSearchProps = (dataIndex) => ({
filterDropdown: ({
setSelectedKeys, selectedKeys, confirm, clearFilters,
}) => (
<div className="custom-filter-dropdown">
<Input
ref={node => { this.searchInput = node; }}
placeholder={`Search ${dataIndex}`}
value={selectedKeys[0]}
onChange={e => setSelectedKeys(e.target.value ? [e.target.value] : [])}
onPressEnter={() => this.handleSearch(selectedKeys, confirm)}
style={{ width: 188, marginBottom: 8, display: 'block' }}
/>
<Button
type="primary"
onClick={() => this.handleSearch(selectedKeys, confirm)}
icon="search"
size="small"
style={{ width: 90, marginRight: 8 }}
>
Search
</Button>
<Button
onClick={() => this.handleReset(clearFilters)}
size="small"
style={{ width: 90 }}
>
Reset
</Button>
</div>
),
filterIcon: filtered => <Icon type="search" style={{ color: filtered ? '#1890ff' : undefined }} />,
onFilter: (value, record) => record[dataIndex].toString().toLowerCase().includes(value.toLowerCase()),
onFilterDropdownVisibleChange: (visible) => {
if (visible) {
setTimeout(() => this.searchInput.select());
}
},
render: (text) => (
<Highlighter
highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}
searchWords={[this.state.searchText]}
autoEscape
textToHighlight={text.toString()}
/>
),
})
handleSearch = (selectedKeys, confirm) => {
confirm(); confirm();
this.setState({ searchText: selectedKeys[0] }); this.setState({ searchText: selectedKeys[0] });
} }
handleReset = clearFilters => () => { handleReset = (clearFilters) => {
clearFilters(); clearFilters();
this.setState({ searchText: '' }); this.setState({ searchText: '' });
} }
@ -60,57 +109,19 @@ class App extends React.Component {
title: 'Name', title: 'Name',
dataIndex: 'name', dataIndex: 'name',
key: 'name', key: 'name',
filterDropdown: ({ width: '30%',
setSelectedKeys, selectedKeys, confirm, clearFilters, ...this.getColumnSearchProps('name'),
}) => (
<div className="custom-filter-dropdown">
<Input
ref={ele => this.searchInput = ele}
placeholder="Search name"
value={selectedKeys[0]}
onChange={e => setSelectedKeys(e.target.value ? [e.target.value] : [])}
onPressEnter={this.handleSearch(selectedKeys, confirm)}
/>
<Button type="primary" onClick={this.handleSearch(selectedKeys, confirm)}>Search</Button>
<Button onClick={this.handleReset(clearFilters)}>Reset</Button>
</div>
),
filterIcon: filtered => <Icon type="smile-o" style={{ color: filtered ? '#108ee9' : '#aaa' }} />,
onFilter: (value, record) => record.name.toLowerCase().includes(value.toLowerCase()),
onFilterDropdownVisibleChange: (visible) => {
if (visible) {
setTimeout(() => {
this.searchInput.focus();
});
}
},
render: (text) => {
const { searchText } = this.state;
return searchText ? (
<span>
{text.split(new RegExp(`(${searchText})`, 'gi')).map((fragment, i) => (
fragment.toLowerCase() === searchText.toLowerCase()
? <span key={i} className="highlight">{fragment}</span> : fragment // eslint-disable-line
))}
</span>
) : text;
},
}, { }, {
title: 'Age', title: 'Age',
dataIndex: 'age', dataIndex: 'age',
key: 'age', key: 'age',
width: '20%',
...this.getColumnSearchProps('age'),
}, { }, {
title: 'Address', title: 'Address',
dataIndex: 'address', dataIndex: 'address',
key: 'address', key: 'address',
filters: [{ ...this.getColumnSearchProps('address'),
text: 'London',
value: 'London',
}, {
text: 'New York',
value: 'New York',
}],
onFilter: (value, record) => record.address.indexOf(value) === 0,
}]; }];
return <Table columns={columns} dataSource={data} />; return <Table columns={columns} dataSource={data} />;
} }
@ -122,21 +133,8 @@ ReactDOM.render(<App />, mountNode);
````css ````css
.custom-filter-dropdown { .custom-filter-dropdown {
padding: 8px; padding: 8px;
border-radius: 6px; border-radius: 4px;
background: #fff; background: #fff;
box-shadow: 0 1px 6px rgba(0, 0, 0, .2); box-shadow: 0 2px 8px rgba(0, 0, 0, .15);
}
.custom-filter-dropdown input {
width: 130px;
margin-right: 8px;
}
.custom-filter-dropdown button {
margin-right: 8px;
}
.highlight {
color: #f50;
} }
```` ````

View File

@ -43,6 +43,11 @@ export interface ColumnProps<T> {
onHeaderCell?: (props: ColumnProps<T>) => any; onHeaderCell?: (props: ColumnProps<T>) => any;
} }
export interface AdditionalCellProps {
onClick?: React.MouseEventHandler<HTMLElement>;
[name: string]: any;
}
export interface TableComponents { export interface TableComponents {
table?: React.ReactType; table?: React.ReactType;
header?: { header?: {

View File

@ -280,8 +280,6 @@
&-thead > tr > th, &-thead > tr > th,
&-tbody > tr > td { &-tbody > tr > td {
padding: @table-padding-vertical @table-padding-horizontal; padding: @table-padding-vertical @table-padding-horizontal;
word-break: break-word;
-ms-word-break: break-all;
} }
&-thead > tr > th.@{table-prefix-cls}-selection-column-custom { &-thead > tr > th.@{table-prefix-cls}-selection-column-custom {

View File

@ -41,13 +41,13 @@ class Demo extends React.Component {
<div> <div>
<Select style={{ width: 200 }} onChange={(val) => { this.setState({ parentPos: val }); }}> <Select style={{ width: 200 }} onChange={(val) => { this.setState({ parentPos: val }); }}>
{positionList.map(pos => ( {positionList.map(pos => (
<Option value={pos}>Parent - {pos}</Option> <Option key={pos} value={pos}>Parent - {pos}</Option>
))} ))}
</Select> </Select>
<Select style={{ width: 200 }} onChange={(val) => { this.setState({ childPos: val }); }}> <Select style={{ width: 200 }} onChange={(val) => { this.setState({ childPos: val }); }}>
{positionList.map(pos => ( {positionList.map(pos => (
<Option value={pos}>Child - {pos}</Option> <Option key={pos} value={pos}>Child - {pos}</Option>
))} ))}
</Select> </Select>

View File

@ -36,9 +36,9 @@ Working on your first Pull Request? You can learn how from this free video serie
To help you get your feet wet and get you familiar with our contribution process, we have a list of [good first issues](https://github.com/ant-design/ant-design/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) that contain bugs or small features that have a relatively limited scope. This is a great place to get started. To help you get your feet wet and get you familiar with our contribution process, we have a list of [good first issues](https://github.com/ant-design/ant-design/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) that contain bugs or small features that have a relatively limited scope. This is a great place to get started.
If you decide to fix an issue, please be sure to check the comment thread in case somebody is already working on a fix. If nobody is working on it at the moment, please leave a comment stating that you intend to work on it so other people dont accidentally duplicate your effort. If you decide to fix an issue, please be sure to check the comment thread in case somebody is already working on a fix. If nobody is working on it at the moment, please leave a comment stating that you intend to work on it so other people don't accidentally duplicate your effort.
If somebody claims an issue but doesnt follow up for more than two weeks, its fine to take over it but you should still leave a comment. If somebody claims an issue but doesn't follow up for more than two weeks, it's fine to take over it but you should still leave a comment.
## Sending a Pull Request ## Sending a Pull Request
@ -48,7 +48,7 @@ The core team is monitoring for pull requests. We will review your pull request
1. Fork the repository and create your branch from [proper branch](#Branch-Organization). 1. Fork the repository and create your branch from [proper branch](#Branch-Organization).
1. Run `npm install` in the repository root. 1. Run `npm install` in the repository root.
1. If youve fixed a bug or added code that should be tested, add tests! 1. If you've fixed a bug or added code that should be tested, add tests!
1. Ensure the test suite passes (npm run test). Tip: `npm test -- --watch TestName` is helpful in development. 1. Ensure the test suite passes (npm run test). Tip: `npm test -- --watch TestName` is helpful in development.
1. Run `npm test -- -u` to update [jest snapshot](http://facebook.github.io/jest/docs/en/snapshot-testing.html#snapshot-testing-with-jest) and commit these changes as well (if has). 1. Run `npm test -- -u` to update [jest snapshot](http://facebook.github.io/jest/docs/en/snapshot-testing.html#snapshot-testing-with-jest) and commit these changes as well (if has).
1. Make sure your code lints (npm run lint). Tip: Lint runs automatically when you `git commit`. 1. Make sure your code lints (npm run lint). Tip: Lint runs automatically when you `git commit`.

View File

@ -32,6 +32,7 @@ Split View | [react-split-pane](https://github.com/tomkp/react-split-pane)
Image Crop | [react-image-crop](https://github.com/DominicTobias/react-image-crop) Image Crop | [react-image-crop](https://github.com/DominicTobias/react-image-crop)
Trend Lines | [react-sparklines](https://github.com/borisyankov/react-sparklines) Trend Lines | [react-sparklines](https://github.com/borisyankov/react-sparklines)
Formatted Input | [text-mask](https://github.com/text-mask/text-mask) Formatted Input | [text-mask](https://github.com/text-mask/text-mask)
Keywords highlight | [react-highlight-words](https://github.com/bvaughn/react-highlight-words)
Animation | [react-move](https://github.com/react-tools/react-move) [Ant Motion](https://motion.ant.design/components/tween-one) Animation | [react-move](https://github.com/react-tools/react-move) [Ant Motion](https://motion.ant.design/components/tween-one)
<style> <style>

View File

@ -32,6 +32,7 @@ Emoji | [emoji-mart](https://github.com/missive/emoji-mart)
图片裁切 | [react-image-crop](https://github.com/DominicTobias/react-image-crop) 图片裁切 | [react-image-crop](https://github.com/DominicTobias/react-image-crop)
趋势线 | [react-sparklines](https://github.com/borisyankov/react-sparklines) 趋势线 | [react-sparklines](https://github.com/borisyankov/react-sparklines)
格式化输入 | [text-mask](https://github.com/text-mask/text-mask) 格式化输入 | [text-mask](https://github.com/text-mask/text-mask)
关键字高亮 | [react-highlight-words](https://github.com/bvaughn/react-highlight-words)
动画 | [react-move](https://github.com/react-tools/react-move) [Ant Motion](https://motion.ant.design/components/tween-one) 动画 | [react-move](https://github.com/react-tools/react-move) [Ant Motion](https://motion.ant.design/components/tween-one)
<style> <style>

View File

@ -20,7 +20,7 @@ title: 设计价值观
作为一份子,自然界的方方面面都会对用户行为产生深远影响,设计者应该从其中汲取灵感,并运用到当下的设计工作中。我们已做了部分探索,并将追求『自然』作为我们未来持之以恒的方向。 作为一份子,自然界的方方面面都会对用户行为产生深远影响,设计者应该从其中汲取灵感,并运用到当下的设计工作中。我们已做了部分探索,并将追求『自然』作为我们未来持之以恒的方向。
- 在行为的执行中,利用[行为分析](https://zhuanlan.zhihu.com/p/41952711)、人工智能、传感器、[元数据](https://zhuanlan.zhihu.com/p/43613398)等一系列方式,辅助用户有效决策、减少用户额外操作,从而节省用户脑力和体力,让人机交互行为更自然。 - 在行为的执行中,利用[行为分析](https://zhuanlan.zhihu.com/p/41952711)、人工智能、[传感器](https://zhuanlan.zhihu.com/p/52648777)、[元数据](https://zhuanlan.zhihu.com/p/43613398)等一系列方式,辅助用户有效决策、减少用户额外操作,从而节省用户脑力和体力,让人机交互行为更自然。
- 在感知和认知中,视觉系统扮演着最重要的角色,通过提炼自然界中的客观规律并运用到界面设计中,从而创建更有层次产品体验;同时,适时加入听觉系统、触觉系统等,创建更多维、更真实的产品体验。详见视觉语言 - 在感知和认知中,视觉系统扮演着最重要的角色,通过提炼自然界中的客观规律并运用到界面设计中,从而创建更有层次产品体验;同时,适时加入听觉系统、触觉系统等,创建更多维、更真实的产品体验。详见视觉语言
> 想了解自然价值观的前世今生,[请移步至专栏](https://zhuanlan.zhihu.com/p/44809866)。 > 想了解自然价值观的前世今生,[请移步至专栏](https://zhuanlan.zhihu.com/p/44809866)。

View File

@ -156,6 +156,7 @@
"react-document-title": "^2.0.3", "react-document-title": "^2.0.3",
"react-dom": "^16.5.2", "react-dom": "^16.5.2",
"react-github-button": "^0.1.11", "react-github-button": "^0.1.11",
"react-highlight-words": "^0.14.0",
"react-infinite-scroller": "^1.2.1", "react-infinite-scroller": "^1.2.1",
"react-intl": "^2.7.0", "react-intl": "^2.7.0",
"react-resizable": "^1.7.5", "react-resizable": "^1.7.5",