diff --git a/CHANGELOG.en-US.md b/CHANGELOG.en-US.md index 6f86c913ce..19996b7cf4 100644 --- a/CHANGELOG.en-US.md +++ b/CHANGELOG.en-US.md @@ -20,7 +20,9 @@ timeline: true `2018-10-26` - 🌟 `Icon`: Add new icons. +- 🌟 `Table`: Add onColumnFilterChange. Callback executed when ColumnFilter is changed. - 💄 `Demo`: Fix bisheng demo site can't expand code by click the button。 +- 💄 `Avatar`: Fix avatar Chinese text positioning is not accurate. ## 0.4.0 diff --git a/CHANGELOG.zh-CN.md b/CHANGELOG.zh-CN.md index 7e84387152..d7c4d0ede6 100644 --- a/CHANGELOG.zh-CN.md +++ b/CHANGELOG.zh-CN.md @@ -20,7 +20,9 @@ timeline: true `2018-10-26` - 🌟 `Icon`: 增加新的图标。 +- 🌟 `Table`: 增加onColumnFilterChange,在表格列过滤器变化时触发。 - 💄 `Demo`: 修复使用bisheng生成的文档网站无法展开样例代码的bug。 +- 💄 `Avatar`: 修复头像中文字定位不准确。 ## 0.4.0 diff --git a/components/avatar/__tests__/Avatar.test.js b/components/avatar/__tests__/Avatar.test.js index 685cb14228..746433206e 100644 --- a/components/avatar/__tests__/Avatar.test.js +++ b/components/avatar/__tests__/Avatar.test.js @@ -8,4 +8,60 @@ describe('Avatar Render', () => { const children = wrapper.find('.ant-avatar-string'); expect(children.length).toBe(1); }); + + it('should render fallback string correctly', () => { + const div = global.document.createElement('div'); + global.document.body.appendChild(div); + + const wrapper = mount(Fallback, { attachTo: div }); + wrapper.instance().setScale = jest.fn(() => wrapper.instance().setState({ scale: 0.5 })); + + wrapper.find('img').simulate('error'); + + const children = wrapper.find('.ant-avatar-string'); + expect(children.length).toBe(1); + expect(children.text()).toBe('Fallback'); + expect(wrapper.instance().setScale).toBeCalled(); + expect(div.querySelector('.ant-avatar-string').style.transform).toContain('scale(0.5)'); + + wrapper.detach(); + global.document.body.removeChild(div); + }); + + it('should handle onError correctly', () => { + const LOAD_FAILURE_SRC = 'http://error.url'; + const LOAD_SUCCESS_SRC = 'https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png'; + + const div = global.document.createElement('div'); + global.document.body.appendChild(div); + + class Foo extends React.Component { + state = { + src: LOAD_FAILURE_SRC, + } + + handleImgError = () => { + this.setState({ + src: LOAD_SUCCESS_SRC, + }); + return false; + } + + render() { + const { src } = this.state; + return ; + } + } + + const wrapper = mount(, { attachTo: div }); + // mock img load Error, since jsdom do not load resource by default + // https://github.com/jsdom/jsdom/issues/1816 + wrapper.find('img').simulate('error'); + + expect(wrapper.find(Avatar).instance().state.isImgExist).toBe(true); + expect(div.querySelector('img').getAttribute('src')).toBe(LOAD_SUCCESS_SRC); + + wrapper.detach(); + global.document.body.removeChild(div); + }); }); diff --git a/components/avatar/__tests__/__snapshots__/demo.test.js.snap b/components/avatar/__tests__/__snapshots__/demo.test.js.snap index 86780003bc..04de7927b6 100644 --- a/components/avatar/__tests__/__snapshots__/demo.test.js.snap +++ b/components/avatar/__tests__/__snapshots__/demo.test.js.snap @@ -201,6 +201,14 @@ exports[`renders ./components/avatar/demo/badge.md correctly 1`] = ` exports[`renders ./components/avatar/demo/basic.md correctly 1`] = `
+ + + @@ -224,6 +232,14 @@ exports[`renders ./components/avatar/demo/basic.md correctly 1`] = `
+ + + diff --git a/components/avatar/demo/basic.md b/components/avatar/demo/basic.md index 169571cff3..2592a075ef 100644 --- a/components/avatar/demo/basic.md +++ b/components/avatar/demo/basic.md @@ -19,11 +19,13 @@ import { Avatar } from 'choerodon-ui'; ReactDOM.render(
+
+ diff --git a/components/avatar/index.tsx b/components/avatar/index.tsx index fc15727d9e..2bf6063f4c 100644 --- a/components/avatar/index.tsx +++ b/components/avatar/index.tsx @@ -6,8 +6,11 @@ import classNames from 'classnames'; export interface AvatarProps { /** Shape of avatar, options:`circle`, `square` */ shape?: 'circle' | 'square'; - /** Size of avatar, options:`large`, `small`, `default` */ - size?: 'large' | 'small' | 'default'; + /* + * Size of avatar, options: `large`, `small`, `default` + * or a custom number size + * */ + size?: 'large' | 'small' | 'default' | number; /** Src of image avatar */ src?: string; /** Type of the Icon to be used in avatar */ @@ -16,6 +19,10 @@ export interface AvatarProps { prefixCls?: string; className?: string; children?: any; + alt?: string; + /* callback when img load error */ + /* return false to prevent Avatar show default fallback behavior, then you can do fallback by your self*/ + onError?: () => boolean; } export interface AvatarState { @@ -46,7 +53,8 @@ export default class Avatar extends React.Component { componentDidUpdate(prevProps: AvatarProps, prevState: AvatarState) { if (prevProps.children !== this.props.children - || (prevState.scale !== this.state.scale && this.state.scale === 1)) { + || (prevState.scale !== this.state.scale && this.state.scale === 1) + || (prevState.isImgExist !== this.state.isImgExist)) { this.setScale(); } } @@ -55,7 +63,8 @@ export default class Avatar extends React.Component { const childrenNode = this.avatarChildren; if (childrenNode) { const childrenWidth = childrenNode.offsetWidth; - const avatarWidth = (ReactDOM.findDOMNode(this) as HTMLElement).getBoundingClientRect().width; + const avatarNode = ReactDOM.findDOMNode(this) as Element; + const avatarWidth = avatarNode.getBoundingClientRect().width; // add 4px gap for each side to get better performance if (avatarWidth - 8 < childrenWidth) { this.setState({ @@ -69,13 +78,21 @@ export default class Avatar extends React.Component { } } - handleImgLoadError = () => this.setState({ isImgExist: false }); + handleImgLoadError = () => { + const { onError } = this.props; + const errorFlag = onError ? onError() : undefined; + if (errorFlag !== false) { + this.setState({ isImgExist: false }); + } + } render() { const { - prefixCls, shape, size, src, icon, className, ...others, + prefixCls, shape, size, src, icon, className, alt, ...others, } = this.props; + const { isImgExist, scale } = this.state; + const sizeCls = classNames({ [`${prefixCls}-lg`]: size === 'large', [`${prefixCls}-sm`]: size === 'small', @@ -83,36 +100,46 @@ export default class Avatar extends React.Component { const classString = classNames(prefixCls, className, sizeCls, { [`${prefixCls}-${shape}`]: shape, - [`${prefixCls}-image`]: src && this.state.isImgExist, + [`${prefixCls}-image`]: src && isImgExist, [`${prefixCls}-icon`]: icon, }); + const sizeStyle: React.CSSProperties = typeof size === 'number' ? { + width: size, + height: size, + lineHeight: `${size}px`, + fontSize: icon ? size / 2 : 18, + } : {}; + let children = this.props.children; - if (src && this.state.isImgExist) { + if (src && isImgExist) { children = ( {alt} ); } else if (icon) { children = ; } else { const childrenNode = this.avatarChildren; - if (childrenNode || this.state.scale !== 1) { + if (childrenNode || scale !== 1) { + const transformString = `scale(${scale}) translateX(-50%)`; const childrenStyle: React.CSSProperties = { - msTransform: `scale(${this.state.scale})`, - WebkitTransform: `scale(${this.state.scale})`, - transform: `scale(${this.state.scale})`, - position: 'absolute', - display: 'inline-block', - left: `calc(50% - ${Math.round(childrenNode.offsetWidth / 2)}px)`, + msTransform: transformString, + WebkitTransform: transformString, + transform: transformString, }; + const sizeChildrenStyle: React.CSSProperties = + typeof size === 'number' ? { + lineHeight: `${size}px`, + } : {}; children = ( this.avatarChildren = span} - style={childrenStyle} + style={{ ...sizeChildrenStyle, ...childrenStyle }} > {children} @@ -129,7 +156,11 @@ export default class Avatar extends React.Component { } } return ( - + {children} ); diff --git a/components/avatar/style/index.less b/components/avatar/style/index.less index bea8f554c1..da52c414dc 100644 --- a/components/avatar/style/index.less +++ b/components/avatar/style/index.less @@ -43,12 +43,18 @@ width: @size; height: @size; line-height: @size; - border-radius: @size / 2; + border-radius: 50%; & > * { line-height: @size; } + &-string { + position: absolute; + left: 50%; + transform-origin: 0 center; + } + &.@{avatar-prefix-cls}-icon { font-size: @font-size; } diff --git a/components/table/ColumnFilter.tsx b/components/table/ColumnFilter.tsx index 077e39df2c..1029c5f118 100644 --- a/components/table/ColumnFilter.tsx +++ b/components/table/ColumnFilter.tsx @@ -9,7 +9,7 @@ import { getColumnKey } from './util'; export interface ColumnFilterProps { prefixCls?: string; columns?: ColumnProps[]; - onColumnFilterChange?: () => void; + onColumnFilterChange?: (item?: any) => void; getPopupContainer?: (triggerNode?: Element) => HTMLElement; } @@ -51,12 +51,12 @@ export default class ColumnFilter extends React.Component { item.item.props.value.hidden = false; - this.fireChange(); + this.fireChange(item); }; onMenuDeselect = (item: any) => { item.item.props.value.hidden = true; - this.fireChange(); + this.fireChange(item); }; onDropdownVisibleChange = (open: boolean) => { @@ -67,10 +67,10 @@ export default class ColumnFilter extends React.Component extends React.Component, TableState< static propTypes = { dataSource: PropTypes.array, empty: PropTypes.oneOfType([PropTypes.node, PropTypes.func]), + onColumnFilterChange: PropTypes.func, columns: PropTypes.array, prefixCls: PropTypes.string, useFixedHeader: PropTypes.bool, @@ -463,7 +464,11 @@ export default class Table extends React.Component, TableState< }); }; - handleColumnFilterChange = () => { + handleColumnFilterChange = (e?: any) => { + const { onColumnFilterChange } = this.props; + if (onColumnFilterChange) { + onColumnFilterChange(e); + } this.forceUpdate(); }; diff --git a/components/table/demo/basic.md b/components/table/demo/basic.md index 1bc7ddaa52..a8324bf098 100644 --- a/components/table/demo/basic.md +++ b/components/table/demo/basic.md @@ -62,5 +62,5 @@ const data = [{ address: 'Sidney No. 1 Lake Park', }]; -ReactDOM.render(, mountNode); +ReactDOM.render(
console.log(item)} />, mountNode); ```` diff --git a/components/table/index.en-US.md b/components/table/index.en-US.md index 51a383d197..f7c42c2b1a 100644 --- a/components/table/index.en-US.md +++ b/components/table/index.en-US.md @@ -75,6 +75,7 @@ const columns = [{ | size | Size of table | `default` \| `middle` \| `small` | `default` | | title | Table title renderer | Function(currentPageData) | | | onChange | Callback executed when pagination, filters or sorter is changed | Function(pagination, filters, sorter) | | +| onColumnFilterChange | Callback executed when ColumnFilter is changed | Function(item) | | | onExpand | Callback executed when the row expand icon is clicked | Function(expanded, record) | | | onExpandedRowsChange | Callback executed when the expanded rows change | Function(expandedRows) | | | onHeaderRow | Set props on per header row | Function(column, index) | - | diff --git a/components/table/index.zh-CN.md b/components/table/index.zh-CN.md index 669a3ded59..5e22db03e1 100644 --- a/components/table/index.zh-CN.md +++ b/components/table/index.zh-CN.md @@ -76,6 +76,7 @@ const columns = [{ | size | 正常或迷你类型,`default` or `small` | string | default | | title | 表格标题 | Function(currentPageData) | | | onChange | 分页、排序、筛选变化时触发 | Function(pagination, filters, sorter) | | +| onColumnFilterChange | 右上角行过滤按钮中选项变化时触发 | Function(item) | | | onExpand | 点击展开图标时触发 | Function(expanded, record) | | | onExpandedRowsChange | 展开的行变化时触发 | Function(expandedRows) | | | onHeaderRow | 设置头部行属性 | Function(column, index) | - | diff --git a/components/table/interface.tsx b/components/table/interface.tsx index 7035ec76e6..a2f817db4d 100644 --- a/components/table/interface.tsx +++ b/components/table/interface.tsx @@ -18,6 +18,7 @@ export interface ColumnProps { align?: 'left' | 'right' | 'center'; filters?: ColumnFilterItem[]; onFilter?: (value: any, record: T, filters?: ColumnFilterItem[]) => boolean; + onColumnFilterChange?: (item: any) => void; filterMultiple?: boolean; filterDropdown?: React.ReactNode; filterDropdownVisible?: boolean; @@ -103,6 +104,7 @@ export interface TableProps { onExpandedRowsChange?: (expandedRowKeys: string[] | number[]) => void; onExpand?: (expanded: boolean, record: T) => void; onChange?: (pagination: TablePaginationConfig | boolean, filters: string[], sorter: Object) => any; + onColumnFilterChange?: (item: any) => void; loading?: boolean | SpinProps; locale?: Object; indentSize?: number;