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 = {
verbose: true,
setupFiles: [
'./tests/setup.js',
],
moduleFileExtensions: [
'ts',
'tsx',
'js',
'jsx',
'json',
'md',
],
modulePathIgnorePatterns: [
'/_site/',
],
testPathIgnorePatterns: [
'/node_modules/',
'dekko',
'node',
],
setupFiles: ['./tests/setup.js'],
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'md'],
modulePathIgnorePatterns: ['/_site/'],
testPathIgnorePatterns: ['/node_modules/', 'dekko', 'node'],
transform: {
'\\.tsx?$': './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}',
],
transformIgnorePatterns,
snapshotSerializers: [
'enzyme-to-json/serializer',
],
snapshotSerializers: ['enzyme-to-json/serializer'],
globals: {
'ts-jest': {
tsConfigFile: './tsconfig.test.json',
tsConfig: './tsconfig.test.json',
},
},
testURL: 'http://localhost',

View File

@ -1,14 +1,7 @@
// jest config for server render environment
module.exports = {
setupFiles: [
'./tests/setup.js',
],
moduleFileExtensions: [
'ts',
'tsx',
'js',
'md',
],
setupFiles: ['./tests/setup.js'],
moduleFileExtensions: ['ts', 'tsx', 'js', 'md'],
transform: {
'\\.tsx?$': './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$',
testEnvironment: 'node',
snapshotSerializers: [
'enzyme-to-json/serializer',
],
snapshotSerializers: ['enzyme-to-json/serializer'],
globals: {
'ts-jest': {
tsConfigFile: './tsconfig.test.json',

View File

@ -3,22 +3,14 @@ import { mount } from 'enzyme';
import BackTop from '..';
describe('BackTop', () => {
beforeAll(() => {
jest.useFakeTimers();
});
afterAll(() => {
jest.useRealTimers();
});
it('should scroll to top after click it', () => {
it('should scroll to top after click it', async () => {
const wrapper = mount(<BackTop visibilityHeight={-1} />);
document.documentElement.scrollTop = 400;
// trigger scroll manually
wrapper.instance().handleScroll();
jest.runAllTimers();
await new Promise(resolve => setTimeout(resolve, 0));
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);
});
});

View File

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

View File

@ -4,6 +4,7 @@ import arrayTreeFilter from 'array-tree-filter';
import classNames from 'classnames';
import omit from 'omit.js';
import KeyCode from 'rc-util/lib/KeyCode';
import { polyfill } from 'react-lifecycles-compat';
import Input from '../input';
import Icon from '../icon';
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
@ -103,6 +104,7 @@ export interface CascaderState {
value: string[];
popupVisible: boolean | undefined;
flattenOptions: CascaderOptionType[][] | undefined;
prevProps: CascaderProps;
}
interface CascaderLocale {
@ -180,9 +182,29 @@ function getFilledFieldNames(props: CascaderProps) {
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(' / ');
export default class Cascader extends React.Component<CascaderProps, CascaderState> {
class Cascader extends React.Component<CascaderProps, CascaderState> {
static defaultProps = {
placeholder: 'Please select',
transitionName: 'slide-up',
@ -193,6 +215,24 @@ export default class Cascader extends React.Component<CascaderProps, CascaderSta
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[];
private input: Input;
@ -204,24 +244,11 @@ export default class Cascader extends React.Component<CascaderProps, CascaderSta
inputValue: '',
inputFocused: false,
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[]) => {
this.setState({ inputValue: '' });
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) {
const { showSearch, notFoundContent } = 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 */
actions?: Array<React.ReactNode>;
/** 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 */
avatar?: React.ReactNode;
/** className of comment */

View File

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

View File

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

View File

@ -1,5 +1,6 @@
import React from 'react';
import { mount } from 'enzyme';
import moment from 'moment';
import DatePicker from '..';
const { RangePicker } = DatePicker;
@ -38,6 +39,7 @@ describe('DatePicker with showTime', () => {
onChange={onChangeFn}
onOk={onOkFn}
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/)] | - |
| 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" |
| ranges | preseted ranges for quick selection | { \[range: string]: [moment](http://momentjs.com/)\[] } \| { \[range: string]: () => [moment](http://momentjs.com/)\[] } | - |
| renderExtraFooter | render extra footer in panel | () => React.ReactNode | - |
| showTime | to provide an additional time selection | object\|boolean | [TimePicker Options](/components/time-picker/#API) |
| showTime.defaultValue | to set default time of selected date, [demo](#components-date-picker-demo-disabled-date) | [moment](http://momentjs.com/)\[] | [moment(), moment()] |
| showTime.defaultValue | to set default time of selected date, [demo](#components-date-picker-demo-disabled-date) | [moment](http://momentjs.com/)\[] | \[moment(), moment()] |
| value | to set date | \[[moment](http://momentjs.com/), [moment](http://momentjs.com/)] | - |
| onCalendarChange | a callback function, can be executed when the start time or the end time of the range is changing | function(dates: [moment, moment], dateStrings: [string, string]) | - |
| onChange | a callback function, can be executed when the selected time is changing | function(dates: [moment, moment], dateStrings: [string, string]) | - |
| onOk | callback when click ok button | function() | - |
| onCalendarChange | a callback function, can be executed when the start time or the end time of the range is changing | function(dates: \[moment, moment], dateStrings: \[string, string]) | - |
| onChange | a callback function, can be executed when the selected time is changing | function(dates: \[moment, moment], dateStrings: \[string, string]) | - |
| onOk | callback when click ok button | function(dates: [moment](http://momentjs.com/)\[]) | - |
<style>
.code-box-demo .ant-calendar-picker {

View File

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

View File

@ -67,7 +67,7 @@ export interface RangePickerProps extends PickerProps {
defaultPickerValue?: RangePickerValue;
onChange?: (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;
ranges?: {
[range: string]: RangePickerPresetRange;

View File

@ -3118,6 +3118,163 @@ exports[`renders ./components/form/demo/validate-other.md correctly 1`] = `
</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
class="ant-row ant-form-item"
>

View File

@ -16,7 +16,8 @@ Demostration for validataion configuration for form controls which are not show
````jsx
import {
Form, Select, InputNumber, Switch, Radio,
Slider, Button, Upload, Icon, Rate,
Slider, Button, Upload, Icon, Rate, Checkbox,
Row, Col,
} from 'antd';
const FormItem = Form.Item;
@ -147,6 +148,25 @@ class Demo extends React.Component {
)}
</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
{...formItemLayout}
label="Rate"

View File

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

View File

@ -25,6 +25,24 @@
.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 {
color: @text-color;
}

View File

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

View File

@ -2,6 +2,7 @@ import * as React from 'react';
import * as PropTypes from 'prop-types';
import classNames from 'classnames';
import shallowEqual from 'shallowequal';
import { polyfill } from 'react-lifecycles-compat';
import Radio from './radio';
import {
RadioGroupProps,
@ -23,7 +24,7 @@ function getCheckedValue(children: React.ReactNode) {
return matched ? { value } : undefined;
}
export default class RadioGroup extends React.Component<RadioGroupProps, RadioGroupState> {
class RadioGroup extends React.Component<RadioGroupProps, RadioGroupState> {
static defaultProps = {
disabled: false,
buttonStyle: 'outline' as RadioGroupButtonStyle,
@ -33,6 +34,22 @@ export default class RadioGroup extends React.Component<RadioGroupProps, RadioGr
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) {
super(props);
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) {
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>;
}
}
polyfill(RadioGroup);
export default RadioGroup;

View File

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

View File

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

View File

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

View File

@ -26,7 +26,7 @@
display: block;
position: absolute;
height: 100%;
max-height: 360px;
max-height: 400px;
width: 100%;
z-index: 4;
.@{spin-prefix-cls}-dot {
@ -75,19 +75,6 @@
&-container {
position: relative;
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 {
content: '';
@ -97,12 +84,27 @@
top: 0;
bottom: 0;
background: @component-background;
opacity: 0.3;
opacity: 0;
pointer-events: none;
transition: all 0.3s;
height: 100%;
width: 100%;
z-index: 10;
}
}
&-blur {
pointer-events: none;
user-select: none;
overflow: hidden;
opacity: 0.5;
&:after {
opacity: 0.4;
pointer-events: auto;
}
}
// tip
// ------------------------------
&-tip {

View File

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

View File

@ -25,6 +25,7 @@ import {
TableComponents,
RowSelectionType,
TableLocale,
AdditionalCellProps,
ColumnProps,
CompareFn,
TableStateFilters,
@ -814,6 +815,7 @@ export default class Table<T> extends React.Component<TableProps<T>, TableState<
const key = this.getColumnKey(column, i) as string;
let filterDropdown;
let sortButton;
let onHeaderCell = column.onHeaderCell;
const sortTitle = this.getColumnTitle(column.title, {}) || locale.sortTitle;
const isSortColumn = this.isSortColumn(column);
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>
);
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 {
...column,
className: classNames(column.className, {
@ -863,13 +884,13 @@ export default class Table<T> extends React.Component<TableProps<T>, TableState<
key="title"
title={sortTitleString}
className={sortButton ? `${prefixCls}-column-sorters` : undefined}
onClick={() => this.toggleSortOrder(column)}
>
{this.renderColumnTitle(column.title)}
{sortButton}
</div>,
filterDropdown,
],
onHeaderCell,
};
});
}

View File

@ -1038,8 +1038,12 @@ exports[`renders ./components/table/demo/custom-filter-panel.md correctly 1`] =
class=""
>
<colgroup>
<col />
<col />
<col
style="width:30%;min-width:30%"
/>
<col
style="width:20%;min-width:20%"
/>
<col />
</colgroup>
<thead
@ -1053,31 +1057,48 @@ exports[`renders ./components/table/demo/custom-filter-panel.md correctly 1`] =
Name
</div>
<i
class="anticon anticon-smile-o ant-table-filter-icon ant-dropdown-trigger"
style="color:#aaa"
class="anticon anticon-search ant-table-filter-icon ant-dropdown-trigger"
title="Filter menu"
>
<svg
aria-hidden="true"
class=""
data-icon="smile"
data-icon="search"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<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>
</i>
</th>
<th
class=""
class="ant-table-column-has-actions ant-table-column-has-filters"
>
<div>
Age
</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
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
</div>
<i
class="anticon anticon-filter ant-dropdown-trigger"
class="anticon anticon-search ant-table-filter-icon ant-dropdown-trigger"
title="Filter menu"
>
<svg
aria-hidden="true"
class=""
data-icon="filter"
data-icon="search"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<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>
</i>
@ -1120,17 +1141,35 @@ exports[`renders ./components/table/demo/custom-filter-panel.md correctly 1`] =
class="ant-table-row-indent indent-level-0"
style="padding-left:0px"
/>
John Brown
</td>
<td
class=""
>
32
<span>
<span
class=""
>
John Brown
</span>
</span>
</td>
<td
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>
</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"
style="padding-left:0px"
/>
Joe Black
</td>
<td
class=""
>
42
<span>
<span
class=""
>
Joe Black
</span>
</span>
</td>
<td
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>
</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"
style="padding-left:0px"
/>
Jim Green
</td>
<td
class=""
>
32
<span>
<span
class=""
>
Jim Green
</span>
</span>
</td>
<td
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>
</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"
style="padding-left:0px"
/>
Jim Red
</td>
<td
class=""
>
32
<span>
<span
class=""
>
Jim Red
</span>
</span>
</td>
<td
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>
</tr>
</tbody>

View File

@ -17,6 +17,7 @@ Implement a customized column search example via `filterDropdown`.
import {
Table, Input, Button, Icon,
} from 'antd';
import Highlighter from 'react-highlight-words';
const data = [{
key: '1',
@ -45,12 +46,60 @@ class App extends React.Component {
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();
this.setState({ searchText: selectedKeys[0] });
}
handleReset = clearFilters => () => {
handleReset = (clearFilters) => {
clearFilters();
this.setState({ searchText: '' });
}
@ -60,57 +109,19 @@ class App extends React.Component {
title: 'Name',
dataIndex: 'name',
key: 'name',
filterDropdown: ({
setSelectedKeys, selectedKeys, confirm, clearFilters,
}) => (
<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;
},
width: '30%',
...this.getColumnSearchProps('name'),
}, {
title: 'Age',
dataIndex: 'age',
key: 'age',
width: '20%',
...this.getColumnSearchProps('age'),
}, {
title: 'Address',
dataIndex: 'address',
key: 'address',
filters: [{
text: 'London',
value: 'London',
}, {
text: 'New York',
value: 'New York',
}],
onFilter: (value, record) => record.address.indexOf(value) === 0,
...this.getColumnSearchProps('address'),
}];
return <Table columns={columns} dataSource={data} />;
}
@ -122,21 +133,8 @@ ReactDOM.render(<App />, mountNode);
````css
.custom-filter-dropdown {
padding: 8px;
border-radius: 6px;
border-radius: 4px;
background: #fff;
box-shadow: 0 1px 6px rgba(0, 0, 0, .2);
}
.custom-filter-dropdown input {
width: 130px;
margin-right: 8px;
}
.custom-filter-dropdown button {
margin-right: 8px;
}
.highlight {
color: #f50;
box-shadow: 0 2px 8px rgba(0, 0, 0, .15);
}
````

View File

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

View File

@ -280,8 +280,6 @@
&-thead > tr > th,
&-tbody > tr > td {
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 {

View File

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

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)
Trend Lines | [react-sparklines](https://github.com/borisyankov/react-sparklines)
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)
<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-sparklines](https://github.com/borisyankov/react-sparklines)
格式化输入 | [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)
<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)。

View File

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