From 73c776e73d1dd1f4739b297597d43ee20c5a401e Mon Sep 17 00:00:00 2001 From: ddcat1115 Date: Thu, 24 Nov 2016 14:03:57 +0800 Subject: [PATCH] feat: `Input` add NumericInput demo & update searchInput demo (#3861) * Input add NumericInput demo update searchInput * Input - add Input.Search - update NumericInput * snap update --- components/input/Input.tsx | 1 + components/input/Search.tsx | 83 ++++++++++++++ components/input/demo/search-input.md | 56 +--------- components/input/demo/tooltip.md | 118 ++++++++++++++++++++ components/input/index.en-US.md | 9 ++ components/input/index.tsx | 2 + components/input/index.zh-CN.md | 9 ++ components/input/style/search-input.less | 62 +++++----- tests/input/__snapshots__/demo.test.js.snap | 46 ++++---- 9 files changed, 281 insertions(+), 105 deletions(-) create mode 100644 components/input/Search.tsx create mode 100644 components/input/demo/tooltip.md diff --git a/components/input/Input.tsx b/components/input/Input.tsx index d178a1f164..41e257b806 100644 --- a/components/input/Input.tsx +++ b/components/input/Input.tsx @@ -57,6 +57,7 @@ export interface InputProps { export default class Input extends Component { static Group: any; + static Search: any; static defaultProps = { disabled: false, prefixCls: 'ant-input', diff --git a/components/input/Search.tsx b/components/input/Search.tsx new file mode 100644 index 0000000000..72be8eeff1 --- /dev/null +++ b/components/input/Search.tsx @@ -0,0 +1,83 @@ +import React from 'react'; +import classNames from 'classnames'; +import Input from './Input'; +import Icon from '../icon'; +import splitObject from '../_util/splitObject'; +import omit from 'omit.js'; + +export interface SearchProps { + className?: string; + placeholder?: string; + prefixCls?: string; + style?: React.CSSProperties; + defaultValue?: any; + value?: any; + onChange?: React.FormEventHandler; + onSearch?: React.FormEventHandler; +} + +export default class Search extends React.Component { + static defaultProps = { + prefixCls: 'ant-input-search', + }; + constructor(props) { + super(props); + let value; + if ('value' in props) { + value = props.value; + } else if ('defaultValue' in props) { + value = props.defaultValue; + } else { + value = ''; + } + this.state = { + value, + focus: false, + }; + } + onChange = (e) => { + if (!('value' in this.props)) { + this.setState({ value: e.target.value }); + } + const onChange = this.props.onChange; + if (onChange) { + onChange(e); + } + } + onSearch = () => { + if (this.state.focus && this.props.onSearch) { + this.props.onSearch(this.state.value); + } else if (!this.state.focus) { + this.setState({ focus: true }); + } + } + render() { + const [{ className, placeholder, prefixCls }, others] = splitObject( + this.props, ['className', 'placeholder', 'prefixCls'] + ); + // Fix https://fb.me/react-unknown-prop + const otherProps = omit(others, [ + 'defaultValue', + 'value', + 'onChange', + 'onSearch', + ]); + const wrapperCls = classNames({ + [`${prefixCls}-wrapper`]: true, + [`${prefixCls}-wrapper-focus`]: this.state.focus, + [className]: !!className, + }); + return ( +
+ + +
+ ); + } +} diff --git a/components/input/demo/search-input.md b/components/input/demo/search-input.md index 76aa93e1f0..2a2d6bba3c 100644 --- a/components/input/demo/search-input.md +++ b/components/input/demo/search-input.md @@ -14,60 +14,10 @@ title: Example of creating a search box by grouping a standard input with a search button. ````jsx -import { Input, Button } from 'antd'; -import classNames from 'classnames'; -const InputGroup = Input.Group; - -const SearchInput = React.createClass({ - getInitialState() { - return { - value: '', - focus: false, - }; - }, - handleInputChange(e) { - this.setState({ - value: e.target.value, - }); - }, - handleFocusBlur(e) { - this.setState({ - focus: e.target === document.activeElement, - }); - }, - handleSearch() { - if (this.props.onSearch) { - this.props.onSearch(this.state.value); - } - }, - render() { - const { style, size, placeholder } = this.props; - const btnCls = classNames({ - 'ant-search-btn': true, - 'ant-search-btn-noempty': !!this.state.value.trim(), - }); - const searchCls = classNames({ - 'ant-search-input': true, - 'ant-search-input-focus': this.state.focus, - }); - return ( -
- - -
-
-
-
- ); - }, -}); +import { Input } from 'antd'; +const InputSearch = Input.Search; ReactDOM.render( - console.log(value)} style={{ width: 200 }} - /> + console.log(value)} /> , mountNode); ```` diff --git a/components/input/demo/tooltip.md b/components/input/demo/tooltip.md new file mode 100644 index 0000000000..b42b69b22d --- /dev/null +++ b/components/input/demo/tooltip.md @@ -0,0 +1,118 @@ +--- +order: 7 +title: + zh-CN: 数值输入框 + en-US: Numeric Input +--- + +## zh-CN + +结合 [Tooltip](/components/tooltip) 组件,实现一个数值输入框,方便内容超长时的全量展现。 + +## en-US + +You can use the Input in conjunction with [Tooltip](/components/tooltip) component to create a Numeric Input, which can provide a good experience for extra-long content display. + +````jsx +import { Input, Tooltip } from 'antd'; + +function formatNumber(value) { + value += ''; + const list = value.split('.'); + const prefix = list[0].charAt(0) === '-' ? '-' : ''; + let num = prefix ? list[0].slice(1) : list[0]; + let result = ''; + while (num.length > 3) { + result = `,${num.slice(-3)}${result}`; + num = num.slice(0, num.length - 3); + } + if (num) { + result = num + result; + } + return `${prefix}${result}${list[1] ? `.${list[1]}` : ''}`; +} + +class NumericInput extends React.Component { + onChange = (e) => { + const { value } = e.target; + const reg = /^-?(0|[1-9][0-9]*)(\.[0-9]*)?$/; + if ((!isNaN(value) && reg.test(value)) || value === '' || value === '-') { + this.props.onChange(value); + } + } + + // '.' at the end or only '-' in the input box. + onBlur = () => { + const { value } = this.props; + if (value.charAt(value.length - 1) === '.' || value === '-') { + this.props.onChange({ value: value.slice(0, -1) }); + } + if (this.props.onBlur) { + this.props.onBlur(); + } + } + + render() { + const { value } = this.props; + const title = (value ? + ( + {value !== '-' ? formatNumber(value) : '-'} + ) : ''); + return ( +
+ + + +
+ ); + } +} + +class NumericInputDemo extends React.Component { + constructor(props) { + super(props); + this.state = { value: '' }; + } + onChange = (value) => { + this.setState({ value }); + } + render() { + const { value } = this.state; + return ( +
+ +
+ ); + } +} + +ReactDOM.render(, mountNode); +```` + +````css +/* to prevent the arrow overflow the popup container, +or the height is not enough when content is empty */ +.numeric-input .ant-tooltip-inner { + min-width: 32px; + min-height: 37px; +} + +.numeric-input .numeric-input-title { + font-size: 14px; +} + +.numeric-input-demo { + width: 120px; +} +```` diff --git a/components/input/index.en-US.md b/components/input/index.en-US.md index e82d2f4497..4ad73e3ec9 100644 --- a/components/input/index.en-US.md +++ b/components/input/index.en-US.md @@ -32,6 +32,15 @@ Keyboard and mouse can be used for providing or changing data. > When `Input` is used in a `Form.Item` context, if the `Form.Item` has the `id` and `options` props defined then `value`, `defaultValue`, and `id` props are automatically set. +#### Input.Search + +| Property | Description | Type | Available Values | Default | +|-----------|------------------------------------------|------------|-------|--------| +| defaultValue | The initial value. | any | | | +| value | The content value. | any | | | +| onChange | The callback function that is triggered when you change the value. | function(e) | | | +| onSearch | The callback function that is triggered when you click on the search-icon or press Enter key. | function | | | + #### Input.Group | Property | Description | Type | Available Values | Default | diff --git a/components/input/index.tsx b/components/input/index.tsx index 9959d42e98..28eeadadce 100644 --- a/components/input/index.tsx +++ b/components/input/index.tsx @@ -1,5 +1,7 @@ import Input from './Input'; import Group from './Group'; +import Search from './Search'; Input.Group = Group; +Input.Search = Search; export default Input; diff --git a/components/input/index.zh-CN.md b/components/input/index.zh-CN.md index 7e5582b77a..f10ddb497f 100644 --- a/components/input/index.zh-CN.md +++ b/components/input/index.zh-CN.md @@ -31,6 +31,15 @@ title: Input > 如果 `Input` 在 `Form.Item` 内,并且 `Form.Item` 设置了 `id` 和 `options` 属性,则 `value` `defaultValue` 和 `id` 属性会被自动设置。 +#### Input.Search + +| 参数 | 说明 | 类型 | 可选值 | 默认值 | +|-----------|------------------------------------------|------------|-------|--------| +| defaultValue | 初始默认值 | any | | | +| value | value 值 | any | | | +| onChange | 值改变的回调 | function(e) | | | +| onSearch | 点击搜索或按下回车键时的回调 | function | | | + #### Input.Group | 参数 | 说明 | 类型 | 可选值 | 默认值 | diff --git a/components/input/style/search-input.less b/components/input/style/search-input.less index 60abf166fe..9731871064 100644 --- a/components/input/style/search-input.less +++ b/components/input/style/search-input.less @@ -3,44 +3,42 @@ @import "../../button/style/mixin"; @import "./mixin"; -.@{ant-prefix}-search-input-wrapper { +.@{ant-prefix}-input-search-wrapper { display: inline-block; vertical-align: middle; + position: relative; + min-width: 24px; + height: @input-height-base; + + .@{ant-prefix}-input-search { + opacity: 0; + width: 0; + transition: all .3s ease; + } + + .@{ant-prefix}-input-search-icon { + position: absolute; + line-height: @input-height-base; + left: 0; + cursor: pointer; + font-size: 24px; + transition: all .3s ease; + + &:hover { + color: @input-hover-border-color; + } + } } -.@{ant-prefix}-search-input { - &.@{ant-prefix}-input-group .@{ant-prefix}-input:first-child, - &.@{ant-prefix}-input-group .@{ant-prefix}-select:first-child { - border-radius: @border-radius-base; - position: absolute; - top: -1px; +.@{ant-prefix}-input-search-wrapper-focus { + .@{ant-prefix}-input-search { + opacity: 1; width: 100%; } - &.@{ant-prefix}-input-group .@{ant-prefix}-input:first-child { - padding-right: 36px; - } - - .@{ant-prefix}-search-btn { - .btn-default; - border-radius: 0 @border-radius-base - 1 @border-radius-base - 1 0; - left: -1px; - position: relative; - border-width: 0 0 0 1px; - z-index: 2; - padding-left: 8px; - padding-right: 8px; - &:hover { - border-color: @border-color-base; - } - } - &&-focus .@{ant-prefix}-search-btn-noempty, - &:hover .@{ant-prefix}-search-btn-noempty { - .btn-primary; - } - .@{ant-prefix}-select-combobox { - .@{ant-prefix}-select-selection__rendered { - margin-right: 29px; - } + .@{ant-prefix}-input-search-icon { + left: 100%; + margin-left: -22px; + font-size: 14px; } } diff --git a/tests/input/__snapshots__/demo.test.js.snap b/tests/input/__snapshots__/demo.test.js.snap index 732ba6d092..96d3891366 100644 --- a/tests/input/__snapshots__/demo.test.js.snap +++ b/tests/input/__snapshots__/demo.test.js.snap @@ -169,28 +169,17 @@ exports[`test renders ./components/input/demo/group.md correctly 1`] = ` exports[`test renders ./components/input/demo/search-input.md correctly 1`] = `
+ class="ant-input-search-wrapper"> - - - -
- -
+ class="ant-input-wrapper"> +
+
`; @@ -230,3 +219,20 @@ exports[`test renders ./components/input/demo/textarea.md correctly 1`] = ` type="textarea" /> `; + +exports[`test renders ./components/input/demo/tooltip.md correctly 1`] = ` +
+
+ + + +
+
+`;