add input

pull/9/head
tangjinzhou 2017-12-06 18:54:20 +08:00
parent fa98dd8086
commit f82be7e667
16 changed files with 946 additions and 9 deletions

View File

@ -25,3 +25,5 @@ export { default as Avatar } from './avatar'
export { default as Badge } from './badge' export { default as Badge } from './badge'
export { default as Tabs } from './tabs' export { default as Tabs } from './tabs'
export { default as Input } from './input'

View File

@ -0,0 +1,36 @@
<template>
<span :class="classes" >
<slot />
</span>
</template>
<script>
export default {
name: 'InputGruop',
props: {
prefixCls: {
default: 'ant-input-group',
type: String,
},
size: {
validator (value) {
return ['small', 'large', 'default'].includes(value)
},
},
compact: Boolean,
},
computed: {
classes () {
const { prefixCls, size, compact = false } = this
return {
[`${prefixCls}`]: true,
[`${prefixCls}-lg`]: size === 'large',
[`${prefixCls}-sm`]: size === 'small',
[`${prefixCls}-compact`]: compact,
}
},
},
methods: {
},
}
</script>

181
components/input/Input.vue Normal file
View File

@ -0,0 +1,181 @@
<script>
import omit from 'omit.js'
// import TextArea from './TextArea';
import inputProps from './inputProps'
function fixControlledValue (value) {
if (typeof value === 'undefined' || value === null) {
return ''
}
return value
}
let inputKey = 1
export default {
name: 'Input',
props: {
...inputProps,
},
data () {
const { value, defaultValue } = this.$props
return {
stateValue: fixControlledValue(value === undefined ? defaultValue : value),
}
},
computed: {
},
watch: {
value (val) {
this.stateValue = fixControlledValue(val)
},
},
methods: {
handleKeyDown (e) {
if (e.keyCode === 13) {
this.$emit('press-enter', e)
}
this.$emit('keydown', e)
},
handleChange (e) {
const { value } = this.$props
if (value === undefined) {
this.stateValue = e.target.value
} else {
this.$forceUpdate()
this.$emit('input', e)
this.$emit('change', e.target.value)
}
},
focus () {
this.$refs.input.focus()
},
blur () {
this.$refs.input.blur()
},
getInputClassName () {
const { prefixCls, size, disabled } = this.$props
return {
[`${prefixCls}`]: true,
[`${prefixCls}-sm`]: size === 'small',
[`${prefixCls}-lg`]: size === 'large',
[`${prefixCls}-disabled`]: disabled,
}
},
renderLabeledInput (children) {
const props = this.props
let { addonBefore, addonAfter } = this.$slots
// Not wrap when there is not addons
if ((!addonBefore && !addonAfter)) {
return children
}
const wrapperClassName = `${props.prefixCls}-group`
const addonClassName = `${wrapperClassName}-addon`
addonBefore = addonBefore ? (
<span class={addonClassName}>
{addonBefore}
</span>
) : null
addonAfter = addonAfter ? (
<span class={addonClassName}>
{addonAfter}
</span>
) : null
const className = {
[`${props.prefixCls}-wrapper`]: true,
[wrapperClassName]: (addonBefore || addonAfter),
}
if (addonBefore || addonAfter) {
return (
<span
class={`${props.prefixCls}-group-wrapper`}
>
<span class={className}>
{addonBefore}
{children}
{addonAfter}
</span>
</span>
)
}
return (
<span class={className}>
{addonBefore}
{children}
{addonAfter}
</span>
)
},
renderLabeledIcon (children) {
const { prefixCls } = this.$props
let { prefix, suffix } = this.$slots
if (!prefix && !suffix) {
return children
}
prefix = prefix ? (
<span class={`${prefixCls}-prefix`}>
{prefix}
</span>
) : null
suffix = suffix ? (
<span class={`${prefixCls}-suffix`}>
{suffix}
</span>
) : null
return (
<span
class={`${prefixCls}-affix-wrapper`}
>
{prefix}
{children}
{suffix}
</span>
)
},
getInputKey () {
const { value } = this
//
if (value !== undefined) {
return inputKey++
} else {
return inputKey
}
},
renderInput () {
const { placeholder, type, readOnly = false, name, id, disabled = false } = this.$props
const { getInputKey, stateValue, getInputClassName, handleKeyDown, handleChange } = this
return this.renderLabeledIcon(
<input
value={stateValue}
type={type}
readOnly={readOnly}
placeholder={placeholder}
name={name}
id={id}
disabled={disabled}
class={getInputClassName()}
onKeydown={handleKeyDown}
onInput={handleChange}
ref='input'
key={getInputKey()}
/>,
)
},
},
render () {
return this.renderLabeledInput(this.renderInput())
},
}
</script>

View File

@ -0,0 +1,61 @@
<script>
import Input, { InputProps } from './Input'
import Icon from '../icon'
export default {
name: 'InputSearch',
props: {
...InputProps,
prefixCls: {
default: 'ant-input-search',
type: String,
},
inputPrefixCls: {
default: 'ant-input',
type: String,
},
},
computed: {
},
methods: {
onSearch (e) {
const eventKeyCode = e.keyCode
if (eventKeyCode === 13) {
// const { onSearch } = this.$props
// if (onSearch) {
// onSearch(this.$refs.input.value)
// }
this.$emit('search', this.$refs.input.value)
this.$refs.input.focus()
}
},
},
render () {
const { inputPrefixCls, prefixCls, ...others } = this.$props
delete others.onSearch
// const searchSuffix = (
// <Icon
// class={`${prefixCls}-icon`}
// onClick={this.onSearch}
// type='search'
// />
// )
return (
<Input
onKeyup={this.onSearch}
{...others}
class={prefixCls}
prefixCls={inputPrefixCls}
ref='input'
>
<Icon
slot='suffix'
class={`${prefixCls}-icon`}
onClick={this.onSearch}
type='search'
/>
</Input>
)
},
}
</script>

View File

View File

@ -0,0 +1,157 @@
// Thanks to https://github.com/andreypopp/react-textarea-autosize/
/**
* calculateNodeHeight(uiTextNode, useCache = false)
*/
const HIDDEN_TEXTAREA_STYLE = `
min-height:0 !important;
max-height:none !important;
height:0 !important;
visibility:hidden !important;
overflow:hidden !important;
position:absolute !important;
z-index:-1000 !important;
top:0 !important;
right:0 !important
`
const SIZING_STYLE = [
'letter-spacing',
'line-height',
'padding-top',
'padding-bottom',
'font-family',
'font-weight',
'font-size',
'text-rendering',
'text-transform',
'width',
'text-indent',
'padding-left',
'padding-right',
'border-width',
'box-sizing',
]
const computedStyleCache = {}
let hiddenTextarea
function calculateNodeStyling (node, useCache = false) {
const nodeRef = (
node.getAttribute('id') ||
node.getAttribute('data-reactid') ||
node.getAttribute('name')
)
if (useCache && computedStyleCache[nodeRef]) {
return computedStyleCache[nodeRef]
}
const style = window.getComputedStyle(node)
const boxSizing = (
style.getPropertyValue('box-sizing') ||
style.getPropertyValue('-moz-box-sizing') ||
style.getPropertyValue('-webkit-box-sizing')
)
const paddingSize = (
parseFloat(style.getPropertyValue('padding-bottom')) +
parseFloat(style.getPropertyValue('padding-top'))
)
const borderSize = (
parseFloat(style.getPropertyValue('border-bottom-width')) +
parseFloat(style.getPropertyValue('border-top-width'))
)
const sizingStyle = SIZING_STYLE
.map(name => `${name}:${style.getPropertyValue(name)}`)
.join(';')
const nodeInfo = {
sizingStyle,
paddingSize,
borderSize,
boxSizing,
}
if (useCache && nodeRef) {
computedStyleCache[nodeRef] = nodeInfo
}
return nodeInfo
}
export default function calculateNodeHeight (
uiTextNode,
useCache = false,
minRows = null,
maxRows = null,
) {
if (!hiddenTextarea) {
hiddenTextarea = document.createElement('textarea')
document.body.appendChild(hiddenTextarea)
}
// Fix wrap="off" issue
// https://github.com/ant-design/ant-design/issues/6577
if (uiTextNode.getAttribute('wrap')) {
hiddenTextarea.setAttribute('wrap', uiTextNode.getAttribute('wrap'))
} else {
hiddenTextarea.removeAttribute('wrap')
}
// Copy all CSS properties that have an impact on the height of the content in
// the textbox
const {
paddingSize, borderSize,
boxSizing, sizingStyle,
} = calculateNodeStyling(uiTextNode, useCache)
// Need to have the overflow attribute to hide the scrollbar otherwise
// text-lines will not calculated properly as the shadow will technically be
// narrower for content
hiddenTextarea.setAttribute('style', `${sizingStyle};${HIDDEN_TEXTAREA_STYLE}`)
hiddenTextarea.value = uiTextNode.value || uiTextNode.placeholder || ''
let minHeight = -Infinity
let maxHeight = Infinity
let height = hiddenTextarea.scrollHeight
let overflowY
if (boxSizing === 'border-box') {
// border-box: add border, since height = content + padding + border
height = height + borderSize
} else if (boxSizing === 'content-box') {
// remove padding, since height = content
height = height - paddingSize
}
if (minRows !== null || maxRows !== null) {
// measure height of a textarea with a single row
hiddenTextarea.value = ''
const singleRowHeight = hiddenTextarea.scrollHeight - paddingSize
if (minRows !== null) {
minHeight = singleRowHeight * minRows
if (boxSizing === 'border-box') {
minHeight = minHeight + paddingSize + borderSize
}
height = Math.max(minHeight, height)
}
if (maxRows !== null) {
maxHeight = singleRowHeight * maxRows
if (boxSizing === 'border-box') {
maxHeight = maxHeight + paddingSize + borderSize
}
overflowY = height > maxHeight ? '' : 'hidden'
height = Math.min(maxHeight, height)
}
}
// Remove scroll bar flash when autosize without maxRows
if (!maxRows) {
overflowY = 'hidden'
}
return { height, minHeight, maxHeight, overflowY }
}

View File

@ -0,0 +1,16 @@
<template>
<div>
<AntInput placeholder="Basic usage" value="123"/>
<AntInput placeholder="Basic usage" defaultValue="mysite"/>
</div>
</template>
<script>
import { Input } from 'antd'
export default {
components: {
AntInput: Input,
},
}
</script>

View File

@ -0,0 +1,9 @@
import Input from './Input'
import Group from './Group'
import Search from './Search'
// import TextArea from './TextArea'
Input.Group = Group
Input.Search = Search
// Input.TextArea = TextArea
export default Input

View File

@ -0,0 +1,39 @@
export default {
prefixCls: {
default: 'ant-input',
type: String,
},
defaultValue: [String, Number],
value: [String, Number],
placeholder: [String, Number],
type: {
default: 'text',
type: String,
},
id: [String, Number],
name: String,
size: {
validator (value) {
return ['small', 'large', 'default'].includes(value)
},
},
maxLength: String,
disabled: {
default: false,
type: Boolean,
},
readOnly: Boolean,
// addonBefore: React.ReactNode,
// addonAfter: React.ReactNode,
// onPressEnter?: React.FormEventHandler<any>;
// onKeyDown?: React.FormEventHandler<any>;
// onChange?: React.ChangeEventHandler<HTMLInputElement>;
// onClick?: React.FormEventHandler<any>;
// onFocus?: React.FormEventHandler<any>;
// onBlur?: React.FormEventHandler<any>;
// autoComplete: String;
// prefix: React.ReactNode,
// suffix: React.ReactNode,
spellCheck: Boolean,
autoFocus: Boolean,
}

View File

@ -0,0 +1,2 @@
import '../../style/index.less'
import './index.less'

View File

@ -0,0 +1,29 @@
@import "../../style/themes/default";
@import "../../style/mixins/index";
@import "./mixin";
@import "./search-input";
// Input styles
.@{ant-prefix}-input {
.input;
}
//== Style for input-group: input with label, with button or dropdown...
.@{ant-prefix}-input-group {
.input-group(~"@{ant-prefix}-input");
&-wrapper {
display: inline-block;
vertical-align: top; // https://github.com/ant-design/ant-design/issues/6403
width: 100%;
}
}
// Input with affix: prefix or suffix
.@{ant-prefix}-input-affix-wrapper {
.input-affix-wrapper(~"@{ant-prefix}-input");
// https://github.com/ant-design/ant-design/issues/6144
.@{ant-prefix}-input {
min-height: 100%; // use min-height, assume that no smaller height to override
}
}

View File

@ -0,0 +1,345 @@
@import "../../style/themes/default";
@import "../../style/mixins/index";
@input-affix-width: 17px;
// size mixins for input
.input-lg() {
padding: @input-padding-vertical-lg @input-padding-horizontal-lg;
height: @input-height-lg;
}
.input-sm() {
padding: @input-padding-vertical-sm @input-padding-horizontal-sm;
height: @input-height-sm;
}
// input status
// == when focus or actived
.active(@color: @outline-color) {
border-color: ~`colorPalette("@{color}", 5)`;
outline: 0;
box-shadow: 0 0 @outline-blur-size @outline-width fade(@color, 20%);
}
// == when hoverd
.hover(@color: @input-hover-border-color) {
border-color: ~`colorPalette("@{color}", 5)`;
}
.disabled() {
background-color: @input-disabled-bg;
opacity: 1;
cursor: not-allowed;
color: @disabled-color;
&:hover {
.hover(@input-border-color);
}
}
// Basic style for input
.input() {
position: relative;
display: inline-block;
padding: @input-padding-vertical-base @input-padding-horizontal-base;
width: 100%;
height: @input-height-base;
font-size: @font-size-base;
line-height: @line-height-base;
color: @input-color;
background-color: @input-bg;
background-image: none;
border: @border-width-base @border-style-base @input-border-color;
border-radius: @border-radius-base;
.placeholder(); // Reset placeholder
transition: all .3s;
&:hover {
.hover();
}
&:focus {
.active();
}
&-disabled {
.disabled();
}
// Reset height for `textarea`s
textarea& {
max-width: 100%; // prevent textearea resize from coming out of its container
height: auto;
vertical-align: bottom;
transition: all .3s, height 0s;
}
// Size
&-lg {
.input-lg();
}
&-sm {
.input-sm();
}
}
// label input
.input-group(@inputClass) {
position: relative;
display: table;
border-collapse: separate;
border-spacing: 0;
width: 100%;
// Undo padding and float of grid classes
&[class*="col-"] {
float: none;
padding-left: 0;
padding-right: 0;
}
> [class*="col-"] {
padding-right: 8px;
&:last-child {
padding-right: 0;
}
}
&-addon,
&-wrap,
> .@{inputClass} {
display: table-cell;
&:not(:first-child):not(:last-child) {
border-radius: 0;
}
}
&-addon,
&-wrap {
width: 1px; // To make addon/wrap as small as possible
white-space: nowrap;
vertical-align: middle;
}
&-wrap > * {
display: block !important;
}
.@{inputClass} {
float: left;
width: 100%;
margin-bottom: 0;
&:focus {
z-index: 1; // Fix https://gw.alipayobjects.com/zos/rmsportal/DHNpoqfMXSfrSnlZvhsJ.png
}
}
&-addon {
padding: @input-padding-vertical-base @input-padding-horizontal-base;
font-size: @font-size-base;
font-weight: normal;
line-height: 1;
color: @input-color;
text-align: center;
background-color: @input-addon-bg;
border: @border-width-base @border-style-base @input-border-color;
border-radius: @border-radius-base;
position: relative;
transition: all .3s;
// Reset Select's style in addon
.@{ant-prefix}-select {
margin: -(@input-padding-vertical-base + 1px) (-@input-padding-horizontal-base); // lesshint spaceAroundOperator: false
.@{ant-prefix}-select-selection {
background-color: inherit;
margin: -1px;
border: @border-width-base @border-style-base transparent;
box-shadow: none;
}
&-open,
&-focused {
.@{ant-prefix}-select-selection {
color: @primary-color;
}
}
}
// Expand addon icon click area
// https://github.com/ant-design/ant-design/issues/3714
> i:only-child:after {
position: absolute;
content: '';
top: 0;
left: 0;
right: 0;
bottom: 0;
}
}
// Reset rounded corners
> .@{inputClass}:first-child,
&-addon:first-child {
border-bottom-right-radius: 0;
border-top-right-radius: 0;
// Reset Select's style in addon
.@{ant-prefix}-select .@{ant-prefix}-select-selection {
border-bottom-right-radius: 0;
border-top-right-radius: 0;
}
}
> .@{inputClass}-affix-wrapper {
&:not(:first-child) .@{inputClass} {
border-bottom-left-radius: 0;
border-top-left-radius: 0;
}
&:not(:last-child) .@{inputClass} {
border-bottom-right-radius: 0;
border-top-right-radius: 0;
}
}
&-addon:first-child {
border-right: 0;
}
&-addon:last-child {
border-left: 0;
}
> .@{inputClass}:last-child,
&-addon:last-child {
border-bottom-left-radius: 0;
border-top-left-radius: 0;
// Reset Select's style in addon
.@{ant-prefix}-select .@{ant-prefix}-select-selection {
border-bottom-left-radius: 0;
border-top-left-radius: 0;
}
}
// Sizing options
&-lg .@{inputClass},
&-lg > &-addon {
.input-lg();
}
&-sm .@{inputClass},
&-sm > &-addon {
.input-sm();
}
// Fix https://github.com/ant-design/ant-design/issues/5754
&-lg .@{ant-prefix}-select-selection--single {
height: @input-height-lg;
}
&-sm .@{ant-prefix}-select-selection--single {
height: @input-height-sm;
}
.@{inputClass}-affix-wrapper {
display: table-cell;
width: 100%;
float: left;
}
&&-compact {
display: block;
.clearfix;
& > * {
border-radius: 0;
border-right-width: 0;
vertical-align: middle;
float: none;
display: inline-block;
}
// Undo float for .ant-input-group .ant-input
.@{inputClass} {
float: none;
z-index: auto;
}
// reset border for Select, DatePicker, AutoComplete, Cascader, Mention, TimePicker
& > .@{ant-prefix}-select > .@{ant-prefix}-select-selection,
& > .@{ant-prefix}-calendar-picker .@{ant-prefix}-input,
& > .@{ant-prefix}-select-auto-complete .@{ant-prefix}-input,
& > .@{ant-prefix}-cascader-picker .@{ant-prefix}-input,
& > .@{ant-prefix}-mention-wrapper .@{ant-prefix}-mention-editor,
& > .@{ant-prefix}-time-picker .@{ant-prefix}-time-picker-input {
border-radius: 0;
border-right-width: 0;
}
& > *:first-child,
& > .@{ant-prefix}-select:first-child > .@{ant-prefix}-select-selection,
& > .@{ant-prefix}-calendar-picker:first-child .@{ant-prefix}-input,
& > .@{ant-prefix}-select-auto-complete:first-child .@{ant-prefix}-input,
& > .@{ant-prefix}-cascader-picker:first-child .@{ant-prefix}-input,
& > .@{ant-prefix}-mention-wrapper:first-child .@{ant-prefix}-mention-editor,
& > .@{ant-prefix}-time-picker:first-child .@{ant-prefix}-time-picker-input {
border-top-left-radius: @border-radius-base;
border-bottom-left-radius: @border-radius-base;
}
& > *:last-child,
& > .@{ant-prefix}-select:last-child > .@{ant-prefix}-select-selection,
& > .@{ant-prefix}-calendar-picker:last-child .@{ant-prefix}-input,
& > .@{ant-prefix}-select-auto-complete:last-child .@{ant-prefix}-input,
& > .@{ant-prefix}-cascader-picker:last-child .@{ant-prefix}-input,
& > .@{ant-prefix}-mention-wrapper:last-child .@{ant-prefix}-mention-editor,
& > .@{ant-prefix}-time-picker:last-child .@{ant-prefix}-time-picker-input {
border-top-right-radius: @border-radius-base;
border-bottom-right-radius: @border-radius-base;
border-right-width: 1px;
}
}
}
.input-affix-wrapper(@inputClass) {
position: relative;
display: inline-block;
width: 100%;
.@{inputClass} {
z-index: 1;
}
&:hover .@{inputClass}:not(.@{inputClass}-disabled) {
.hover();
}
.@{inputClass}-prefix,
.@{inputClass}-suffix {
position: absolute;
top: 50%;
transform: translateY(-50%);
z-index: 2;
line-height: 0;
color: @input-color;
}
.@{inputClass}-prefix {
left: @input-padding-horizontal-base;
}
.@{inputClass}-suffix {
right: @input-padding-horizontal-base;
}
.@{inputClass}:not(:first-child) {
padding-left: @input-padding-horizontal-base + @input-affix-width;
}
.@{inputClass}:not(:last-child) {
padding-right: @input-padding-horizontal-base + @input-affix-width;
}
}

View File

@ -0,0 +1,58 @@
@import "../../style/themes/default";
@import "../../style/mixins/index";
@import "../../button/style/mixin";
@import "./mixin";
.@{ant-prefix}-input-search-icon {
cursor: pointer;
transition: all .3s;
font-size: 14px;
&:hover {
color: @input-hover-border-color;
}
}
// code blow is keeped for compatibility
// for this demo: http://1x.ant.design/components/select/#components-select-demo-search-box
// do not delete until 3.x
.@{ant-prefix}-search-input-wrapper {
display: inline-block;
vertical-align: middle;
}
.@{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;
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 - 1px @border-radius-base - 1px 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;
}
}
}

View File

@ -9,3 +9,4 @@ import './pagination/style'
import './avatar/style' import './avatar/style'
import './badge/style' import './badge/style'
import './tabs/style' import './tabs/style'
import './input/style'

View File

@ -5,19 +5,19 @@ Button | done
Icon | done Icon | done
Checkbox | done Checkbox | done
Radio | done Radio | done
AutoComplete Tabs | done
Calendar Tag | done
Carousel Carousel
DatePicker Mention
Form
Input Input
InputNumber InputNumber
AutoComplete
Select Select
Tabs
Tag
Mention
TimePicker
Upload Upload
Form
Calendar
DatePicker
TimePicker
##万 ##万
Grid Grid

View File

@ -76,6 +76,7 @@
"dependencies": { "dependencies": {
"add-dom-event-listener": "^1.0.2", "add-dom-event-listener": "^1.0.2",
"eslint-plugin-vue": "^3.13.0", "eslint-plugin-vue": "^3.13.0",
"lodash.debounce": "^4.0.8" "lodash.debounce": "^4.0.8",
"omit.js": "^1.0.0"
} }
} }